@indigoai-us/hq-cli 5.108.5 → 5.108.7
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 +134 -0
- package/dist/commands/auth.js +11 -4
- package/dist/commands/feedback.d.ts +38 -0
- package/dist/commands/feedback.js +80 -0
- package/dist/commands/signals.js +12 -0
- package/dist/commands/sources.js +12 -0
- package/dist/commands/whoami.js +7 -2
- package/dist/utils/cognito-session.d.ts +58 -16
- package/dist/utils/cognito-session.js +229 -39
- package/dist/utils/feedback-diagnostics.d.ts +11 -0
- package/dist/utils/feedback-log-bundle.d.ts +149 -0
- package/dist/utils/feedback-log-bundle.js +430 -0
- package/dist/utils/feedback-logs.d.ts +329 -0
- package/dist/utils/feedback-logs.js +669 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.108.7] — 2026-09-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fresh agent boxes and Outposts no longer fail `hq auth refresh` with "No
|
|
10
|
+
valid session: no cached session" when `HQ_MACHINE_CREDS_FILE` is unset.
|
|
11
|
+
Regression since 5.108.2: machine-token minting required that env (or an
|
|
12
|
+
explicit `{ tokenSource: "machine" }`), but hq-pro cloud-init never exports
|
|
13
|
+
it, so bootstrap aborted after sixty refresh failures. When the env is
|
|
14
|
+
unset, there is no usable person session at `~/.hq/cognito-tokens.json`, and
|
|
15
|
+
the default `~/.hq-agent/machine-creds.json` is readable, the CLI now mints
|
|
16
|
+
from that file (same daemon-state-dir cache as the env opt-in) and logs
|
|
17
|
+
`machine identity from default creds file`. A present person session still
|
|
18
|
+
wins so a human login is never flipped onto machine tokens; explicit
|
|
19
|
+
`tokenSource: "person"` never uses machine creds. `hq auth status` and
|
|
20
|
+
`hq whoami` report the resolved token source.
|
|
21
|
+
|
|
22
|
+
## [5.108.6] — 2026-09-05
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `hq feedback bug|feature` now uploads a full log bundle alongside the report,
|
|
27
|
+
so a bug report carries the submitter's whole recent log history rather than a
|
|
28
|
+
16 KiB sliver of it. Measured against a real `~/.hq`: 21 MB of logs across 21
|
|
29
|
+
files, compressed to 1.3 MB — roughly 500x what the inline path can hold.
|
|
30
|
+
|
|
31
|
+
The inline `diagnostics.logs` summary is unchanged and still ships in the
|
|
32
|
+
request body. It cannot be grown: the server rejects a request over 64 KiB and
|
|
33
|
+
stores `diagnostics` in a DynamoDB item, which caps at 400 KB. So the bundle
|
|
34
|
+
goes out of band — gzipped and uploaded direct to S3 through a presigned URL,
|
|
35
|
+
the same path screenshots already use — and the report carries only a
|
|
36
|
+
reference to it.
|
|
37
|
+
|
|
38
|
+
Three properties are load-bearing and are covered by tests that fail if any is
|
|
39
|
+
removed:
|
|
40
|
+
|
|
41
|
+
- **Same allowlist.** Discovery is reused from the inline collector verbatim.
|
|
42
|
+
The bundle carries more OF the same files, never more files. Credential
|
|
43
|
+
material lives outside `~/.hq` (`~/.codex/auth.json`,
|
|
44
|
+
`~/.hq-agent/machine-creds.json`), so widening discovery — not raising the
|
|
45
|
+
size — is what would turn this into an exfiltration bug.
|
|
46
|
+
- **Same redaction.** Every chunk passes through the existing redactor before
|
|
47
|
+
compression. A chunk boundary is held open rather than cut through a PEM
|
|
48
|
+
block, and an over-long line is cut on whitespace, so a split can never
|
|
49
|
+
bisect a token and let the halves escape a pattern that would have matched
|
|
50
|
+
the whole.
|
|
51
|
+
- **Bounded memory.** 50 MB compressed is roughly a gigabyte of raw text.
|
|
52
|
+
Nothing is fully materialised: files are read in slices, redacted, and
|
|
53
|
+
streamed into gzip, retaining only the compressed output.
|
|
54
|
+
|
|
55
|
+
Ordering is the truncation policy — status documents first, then logs newest
|
|
56
|
+
first, so a capped run keeps what a triager reads first. Oversized files are
|
|
57
|
+
read from the tail, because the end of a log is what explains a failure.
|
|
58
|
+
|
|
59
|
+
`--no-logs` and `HQ_FEEDBACK_LOGS=0` disable the bundle and the inline logs
|
|
60
|
+
together. Every failure is silent and non-fatal: a missing endpoint, a
|
|
61
|
+
disabled bucket, a refused presign, or a failed upload all degrade to
|
|
62
|
+
submitting without the bundle rather than costing the user their bug report.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- `hq feedback` debug capture now also collects three more `~/.hq` sources, all
|
|
67
|
+
small and all high-signal for the failures they describe:
|
|
68
|
+
|
|
69
|
+
- **`locks/`** — held operation locks and background-work claims. This is the
|
|
70
|
+
one place where the FILENAME is the diagnosis: a `…stale-claim…` entry names
|
|
71
|
+
the operation, the process that abandoned it, and the fact that the claim
|
|
72
|
+
went stale, which is the answer to "sync/reindex is stuck". A hung process
|
|
73
|
+
writes nothing to a log, so this is otherwise invisible. Zero-byte lock
|
|
74
|
+
files are kept for their names rather than skipped as empty.
|
|
75
|
+
- **`jobs/`** — scheduled-job status, last reconcile, and probe attempts,
|
|
76
|
+
answering "my scheduled job never ran". Nested, so this is the collector's
|
|
77
|
+
only directory walk; it is bounded in depth (3) and file count (20), and
|
|
78
|
+
refuses to traverse a symlinked directory so it cannot escape `~/.hq`.
|
|
79
|
+
- Three more root status documents: `outpost-session-heartbeat.json` (Outpost
|
|
80
|
+
liveness), `version-check.json` (why an update is not offered), and
|
|
81
|
+
`plan-limit-nag.json`.
|
|
82
|
+
|
|
83
|
+
Deliberately still excluded: the two multi-megabyte telemetry cursors, the
|
|
84
|
+
`sync-state-v3/` internals, `backups/`, and everything outside `~/.hq` —
|
|
85
|
+
notably Claude Code session transcripts, which are conversation content and
|
|
86
|
+
do not belong on a bug report.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Runtime bumped to `@indigoai-us/hq-cloud` ~6.16.11: personal-vault sync no
|
|
91
|
+
longer downloads `workspace/.session-logs/` from other machines (push-only),
|
|
92
|
+
and `hq reindex` now prunes local session-log copies 7 days after the vault
|
|
93
|
+
confirms them (`HQ_SESSION_LOG_LOCAL_RETENTION_DAYS`, `off` to disable).
|
|
94
|
+
|
|
95
|
+
|
|
5
96
|
## [5.108.5] — 2026-09-04
|
|
6
97
|
|
|
7
98
|
### Added
|
|
@@ -10,6 +101,49 @@
|
|
|
10
101
|
through HQ's managed source pipeline, with immutable idempotent replay and
|
|
11
102
|
explicit conflict detection when an external source ID is reused with
|
|
12
103
|
different content.
|
|
104
|
+
- `hq feedback bug|feature` now attaches bounded, redacted debug evidence from
|
|
105
|
+
the submitter's `~/.hq` directory to the submission's `diagnostics`, so a bug
|
|
106
|
+
report carries the context a triager would otherwise have to ask for. Two
|
|
107
|
+
classes are collected:
|
|
108
|
+
|
|
109
|
+
- **Log tails** — `*.log` and `*.jsonl` directly inside `~/.hq/logs/`,
|
|
110
|
+
including rotated generations (`hq-sync.log.1`, `.2`, `.3`), plus
|
|
111
|
+
`boot-sync.log` and `boot-capture.log` at the `~/.hq` root. This covers the
|
|
112
|
+
hq-cloud sync engine and the desktop sync app (which share
|
|
113
|
+
`logs/hq-sync.log`) and the CLI's MCP registry audit trail
|
|
114
|
+
(`logs/mcp-registry.log`). Rotated generations matter most right after a
|
|
115
|
+
rotation, when the active file is nearly empty and the history a triager
|
|
116
|
+
needs sits in `.1`. Any future log dropped into `~/.hq/logs/` is picked up
|
|
117
|
+
automatically.
|
|
118
|
+
- **State snapshots** — the small JSON status documents that say where sync
|
|
119
|
+
and client health stand: `sync-progress.json` (phase, file counts, conflict
|
|
120
|
+
count), `sync-version.json`, `cli-client-health.json` (failure streak),
|
|
121
|
+
`cli-client-health.observation.json`, and the per-company
|
|
122
|
+
`sync-journal.<slug>.json` locators. These are collected first, since they
|
|
123
|
+
answer questions a log tail cannot, and the log budget takes what remains.
|
|
124
|
+
|
|
125
|
+
Eligibility is an explicit allowlist in both classes. The rest of `~/.hq`
|
|
126
|
+
(`cognito-tokens.json`, `deploy-passwords.json`, `secrets-cache/`) is never
|
|
127
|
+
read, the enormous `sync-journal.*.json.last-good` snapshots are excluded by
|
|
128
|
+
suffix, and eligibility is decided with `lstat` so a symlink wearing an
|
|
129
|
+
eligible name cannot be used to reach a credential file.
|
|
130
|
+
|
|
131
|
+
Every tail is redacted before it is measured or sent (JWTs, AWS key ids,
|
|
132
|
+
GitHub and Slack tokens, `Bearer` values, SigV4 presign signatures, PEM
|
|
133
|
+
private-key blocks, and `key=value` pairs for secret-shaped names). The total
|
|
134
|
+
is budgeted against the headroom left under the server's 64 KiB request-body
|
|
135
|
+
cap and re-measured against the real serialized envelope after attaching, so
|
|
136
|
+
a large report can never be turned into a 413 by its own diagnostics.
|
|
137
|
+
|
|
138
|
+
Opt out per invocation with `--no-logs`, or globally with
|
|
139
|
+
`HQ_FEEDBACK_LOGS=0`.
|
|
140
|
+
|
|
141
|
+
### Fixed
|
|
142
|
+
|
|
143
|
+
- `hq` self-update no longer loops. After the CLI updates to `@latest` and a
|
|
144
|
+
re-run produces no effective version change, the version check records that
|
|
145
|
+
outcome and converges instead of re-triggering the updater, so 5.105.x (and
|
|
146
|
+
later) installs stop repeatedly self-updating (#494).
|
|
13
147
|
|
|
14
148
|
## [5.108.4] — 2026-09-04
|
|
15
149
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* by the deploy + sync skills.
|
|
15
15
|
*/
|
|
16
16
|
import chalk from "chalk";
|
|
17
|
-
import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring,
|
|
18
|
-
import { DEFAULT_COGNITO, refreshCachedSession, } from "../utils/cognito-session.js";
|
|
17
|
+
import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring, loadMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
|
|
18
|
+
import { DEFAULT_COGNITO, refreshCachedSession, resolveCognitoTokenSource, } from "../utils/cognito-session.js";
|
|
19
19
|
import { cognitoConfigForLoginProvider } from "../utils/login-provider.js";
|
|
20
20
|
import { callbackPortBusyGuidance, DEFAULT_CALLBACK_PORT, isCallbackPortBusy, } from "../utils/callback-port-busy.js";
|
|
21
21
|
/**
|
|
@@ -56,7 +56,7 @@ export function registerAuthCommands(program) {
|
|
|
56
56
|
.description("Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally")
|
|
57
57
|
.option("--provider <provider>", "OAuth provider to use: google, microsoft, or picker")
|
|
58
58
|
.action(async (options) => {
|
|
59
|
-
if (
|
|
59
|
+
if (resolveCognitoTokenSource() === "machine") {
|
|
60
60
|
console.log(chalk.green(`Running as ${machineIdentityLabel()} — sessions mint automatically; no browser login needed.`));
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
@@ -119,16 +119,22 @@ export function registerAuthCommands(program) {
|
|
|
119
119
|
.command("status")
|
|
120
120
|
.description("Show whether a valid HQ session is cached")
|
|
121
121
|
.action(() => {
|
|
122
|
-
|
|
122
|
+
// Effective source: env opt-in, fresh-box default-creds when no usable
|
|
123
|
+
// person session, or after a definitive person-refresh rejection marked
|
|
124
|
+
// the person cache rejected (transient refresh errors still report person).
|
|
125
|
+
const tokenSource = resolveCognitoTokenSource();
|
|
126
|
+
const machine = tokenSource === "machine";
|
|
123
127
|
const cached = loadCachedTokens();
|
|
124
128
|
if (!cached) {
|
|
125
129
|
if (machine) {
|
|
126
130
|
// No cached session yet, but machine creds mint one on demand —
|
|
127
131
|
// report ready, not signed-out.
|
|
128
132
|
console.log(chalk.green(`${machineIdentityLabel()} — no cached session yet (mints automatically on first use)`));
|
|
133
|
+
console.log(chalk.dim(`token source: ${tokenSource}`));
|
|
129
134
|
return;
|
|
130
135
|
}
|
|
131
136
|
console.log(chalk.yellow("No cached HQ session — run `hq auth login`"));
|
|
137
|
+
console.log(chalk.dim(`token source: ${tokenSource}`));
|
|
132
138
|
process.exit(1);
|
|
133
139
|
}
|
|
134
140
|
const claims = peekIdToken(cached.idToken);
|
|
@@ -140,6 +146,7 @@ export function registerAuthCommands(program) {
|
|
|
140
146
|
console.log(expiring
|
|
141
147
|
? chalk.yellow(`${label}HQ session cached but expiring (expiresAt=${cached.expiresAt})`)
|
|
142
148
|
: chalk.green(`${label}HQ session valid (expiresAt=${cached.expiresAt})`));
|
|
149
|
+
console.log(chalk.dim(`token source: ${tokenSource}`));
|
|
143
150
|
});
|
|
144
151
|
}
|
|
145
152
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { type DiagnosticsBlob } from "../utils/feedback-diagnostics.js";
|
|
3
|
+
import { collectFeedbackLogs, feedbackLogsEnabled } from "../utils/feedback-logs.js";
|
|
4
|
+
import { type LogBundleRef } from "../utils/feedback-log-bundle.js";
|
|
2
5
|
export declare const BODY_MAX_BYTES: number;
|
|
6
|
+
/**
|
|
7
|
+
* Mirror of the server's `MAX_BODY_BYTES` in hq-pro
|
|
8
|
+
* `src/vault-service/handlers/feedback.ts`, which rejects the WHOLE request
|
|
9
|
+
* body over this size with a 413. Note this is the same number as
|
|
10
|
+
* `BODY_MAX_BYTES` above but a different measurement: that one bounds the
|
|
11
|
+
* markdown body alone, this one bounds the serialized JSON envelope. Debug
|
|
12
|
+
* logs are budgeted against whatever headroom is left between them.
|
|
13
|
+
*/
|
|
14
|
+
export declare const REQUEST_MAX_BYTES: number;
|
|
3
15
|
export interface FeedbackResult {
|
|
4
16
|
id: string;
|
|
5
17
|
}
|
|
@@ -11,8 +23,34 @@ export interface FeedbackSubmitOptions {
|
|
|
11
23
|
token: string;
|
|
12
24
|
/** S3 object keys of already-uploaded screenshots (see uploadScreenshots). */
|
|
13
25
|
screenshots?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Attach redacted tails of the submitter's `~/.hq` logs. Defaults to true;
|
|
28
|
+
* `false` (from `--no-logs`) skips collection entirely. Even when true, the
|
|
29
|
+
* user's `HQ_FEEDBACK_LOGS` env override still applies.
|
|
30
|
+
*/
|
|
31
|
+
includeLogs?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Reference to an already-uploaded log bundle (see uploadLogBundle). Carries
|
|
34
|
+
* the full log history that the inline `diagnostics.logs` blob cannot — the
|
|
35
|
+
* inline blob is bounded by the request cap and DynamoDB's item limit, so it
|
|
36
|
+
* stays a summary and this carries the rest.
|
|
37
|
+
*/
|
|
38
|
+
logBundle?: LogBundleRef;
|
|
14
39
|
}
|
|
15
40
|
export declare function readBodyFile(bodyFile: string, stdin?: NodeJS.ReadableStream): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Attach redacted `~/.hq` log tails to `diagnostics` if, and only if, they fit
|
|
43
|
+
* inside the server's request-body cap.
|
|
44
|
+
*
|
|
45
|
+
* Mutates `diagnostics` in place (it is already referenced by `requestBody`,
|
|
46
|
+
* so the measurement below sees the attached logs). Best-effort throughout: a
|
|
47
|
+
* collection failure leaves the submission untouched rather than failing a bug
|
|
48
|
+
* report over its own diagnostics.
|
|
49
|
+
*/
|
|
50
|
+
export declare function attachDebugLogs(requestBody: Record<string, unknown>, diagnostics: DiagnosticsBlob, enabled: boolean, deps?: {
|
|
51
|
+
collect?: typeof collectFeedbackLogs;
|
|
52
|
+
envEnabled?: typeof feedbackLogsEnabled;
|
|
53
|
+
}): void;
|
|
16
54
|
export declare function submitFeedback(opts: FeedbackSubmitOptions): Promise<FeedbackResult>;
|
|
17
55
|
export declare function registerFeedbackCommand(program: Command): void;
|
|
18
56
|
//# sourceMappingURL=feedback.d.ts.map
|
|
@@ -4,7 +4,24 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
|
4
4
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
5
5
|
import { collectDiagnostics } from "../utils/feedback-diagnostics.js";
|
|
6
6
|
import { MAX_SCREENSHOTS, uploadScreenshots } from "../utils/feedback-screenshots.js";
|
|
7
|
+
import { FEEDBACK_LOGS_MAX_TOTAL_BYTES, FEEDBACK_LOGS_MIN_USEFUL_BYTES, collectFeedbackLogs, feedbackLogsEnabled, } from "../utils/feedback-logs.js";
|
|
8
|
+
import { uploadLogBundle } from "../utils/feedback-log-bundle.js";
|
|
7
9
|
export const BODY_MAX_BYTES = 64 * 1024;
|
|
10
|
+
/**
|
|
11
|
+
* Mirror of the server's `MAX_BODY_BYTES` in hq-pro
|
|
12
|
+
* `src/vault-service/handlers/feedback.ts`, which rejects the WHOLE request
|
|
13
|
+
* body over this size with a 413. Note this is the same number as
|
|
14
|
+
* `BODY_MAX_BYTES` above but a different measurement: that one bounds the
|
|
15
|
+
* markdown body alone, this one bounds the serialized JSON envelope. Debug
|
|
16
|
+
* logs are budgeted against whatever headroom is left between them.
|
|
17
|
+
*/
|
|
18
|
+
export const REQUEST_MAX_BYTES = 64 * 1024;
|
|
19
|
+
/**
|
|
20
|
+
* Headroom held back for the `"logs":{...}` JSON envelope itself — its keys,
|
|
21
|
+
* braces, and the escaping of the tail text. The final size is re-measured
|
|
22
|
+
* after attaching, so this reserve only needs to be roughly right.
|
|
23
|
+
*/
|
|
24
|
+
const LOGS_ENVELOPE_RESERVE_BYTES = 1024;
|
|
8
25
|
export async function readBodyFile(bodyFile, stdin) {
|
|
9
26
|
if (bodyFile === "-") {
|
|
10
27
|
const stream = stdin ?? process.stdin;
|
|
@@ -23,6 +40,44 @@ export async function readBodyFile(bodyFile, stdin) {
|
|
|
23
40
|
}
|
|
24
41
|
return fs.promises.readFile(bodyFile, "utf-8");
|
|
25
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Attach redacted `~/.hq` log tails to `diagnostics` if, and only if, they fit
|
|
45
|
+
* inside the server's request-body cap.
|
|
46
|
+
*
|
|
47
|
+
* Mutates `diagnostics` in place (it is already referenced by `requestBody`,
|
|
48
|
+
* so the measurement below sees the attached logs). Best-effort throughout: a
|
|
49
|
+
* collection failure leaves the submission untouched rather than failing a bug
|
|
50
|
+
* report over its own diagnostics.
|
|
51
|
+
*/
|
|
52
|
+
export function attachDebugLogs(requestBody, diagnostics, enabled, deps = {}) {
|
|
53
|
+
const collect = deps.collect ?? collectFeedbackLogs;
|
|
54
|
+
const envEnabled = deps.envEnabled ?? feedbackLogsEnabled;
|
|
55
|
+
if (!enabled || !envEnabled())
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
const baseBytes = Buffer.byteLength(JSON.stringify(requestBody), "utf8");
|
|
59
|
+
const available = REQUEST_MAX_BYTES - baseBytes - LOGS_ENVELOPE_RESERVE_BYTES;
|
|
60
|
+
if (available < FEEDBACK_LOGS_MIN_USEFUL_BYTES)
|
|
61
|
+
return;
|
|
62
|
+
const logs = collect({
|
|
63
|
+
budgetBytes: Math.min(available, FEEDBACK_LOGS_MAX_TOTAL_BYTES),
|
|
64
|
+
});
|
|
65
|
+
if (!logs)
|
|
66
|
+
return;
|
|
67
|
+
diagnostics.logs = logs;
|
|
68
|
+
// Final authority: re-measure the ACTUAL envelope. JSON escaping of the
|
|
69
|
+
// tail text (quotes, newlines, control chars) can expand it well past the
|
|
70
|
+
// raw byte count the budget counted, so the estimate above is necessary
|
|
71
|
+
// but not sufficient. If we overshot, drop the logs rather than 413.
|
|
72
|
+
if (Buffer.byteLength(JSON.stringify(requestBody), "utf8") > REQUEST_MAX_BYTES) {
|
|
73
|
+
delete diagnostics.logs;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Never let diagnostics collection break the submission.
|
|
78
|
+
delete diagnostics.logs;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
26
81
|
export async function submitFeedback(opts) {
|
|
27
82
|
// Validate the title locally, symmetric with the body check below. Commander's
|
|
28
83
|
// `requiredOption("--title")` only requires the flag to be PRESENT — an empty
|
|
@@ -54,6 +109,18 @@ export async function submitFeedback(opts) {
|
|
|
54
109
|
if (opts.screenshots && opts.screenshots.length > 0) {
|
|
55
110
|
requestBody.screenshots = opts.screenshots;
|
|
56
111
|
}
|
|
112
|
+
// The bundle reference is four small fields, so unlike the inline logs below
|
|
113
|
+
// it needs no budgeting — but it is added BEFORE attachDebugLogs so those
|
|
114
|
+
// bytes are counted against the inline budget rather than overrunning it.
|
|
115
|
+
if (opts.logBundle) {
|
|
116
|
+
requestBody.logBundle = opts.logBundle;
|
|
117
|
+
}
|
|
118
|
+
// Debug logs are attached LAST and sized against the headroom the rest of
|
|
119
|
+
// the request left behind. The server 413s the whole body over 64 KiB, so a
|
|
120
|
+
// fixed log budget would turn a previously-working large-body submission
|
|
121
|
+
// into a hard failure. Measuring the real serialized envelope — the exact
|
|
122
|
+
// bytes vaultApiFetch will send — is what makes that impossible.
|
|
123
|
+
attachDebugLogs(requestBody, diagnostics, opts.includeLogs !== false);
|
|
57
124
|
const res = await vaultApiFetch({
|
|
58
125
|
token: opts.token,
|
|
59
126
|
path: "/v1/feedback",
|
|
@@ -81,6 +148,9 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
81
148
|
.requiredOption("--body-file <path>", "Path to a markdown file with the body; use - to read from stdin")
|
|
82
149
|
.option("--company <slug>", "Company slug to associate with the report")
|
|
83
150
|
.option("--screenshot <path>", `Attach a screenshot (repeatable, up to ${MAX_SCREENSHOTS}; .png/.jpg/.jpeg/.webp/.gif)`, (value, prev) => [...prev, value], [])
|
|
151
|
+
// Commander turns `--no-logs` into a boolean `logs` that defaults to true,
|
|
152
|
+
// so the default is "attach logs" and the flag is the opt-out.
|
|
153
|
+
.option("--no-logs", "Do not attach redacted tails of your ~/.hq log files (also: HQ_FEEDBACK_LOGS=0)")
|
|
84
154
|
.action(async (opts) => {
|
|
85
155
|
try {
|
|
86
156
|
const token = await ensureCognitoToken({ interactive: false });
|
|
@@ -91,6 +161,14 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
91
161
|
paths: opts.screenshot ?? [],
|
|
92
162
|
token,
|
|
93
163
|
});
|
|
164
|
+
// Full-history logs go direct to S3 before the submission that
|
|
165
|
+
// references them. Best-effort throughout: if the endpoint is absent
|
|
166
|
+
// (older server), the bucket is unconfigured, or the upload fails,
|
|
167
|
+
// this resolves undefined and the report still carries inline logs.
|
|
168
|
+
const logBundle = await uploadLogBundle({
|
|
169
|
+
token,
|
|
170
|
+
enabled: opts.logs !== false && feedbackLogsEnabled(),
|
|
171
|
+
});
|
|
94
172
|
const result = await submitFeedback({
|
|
95
173
|
type,
|
|
96
174
|
title: opts.title,
|
|
@@ -98,6 +176,8 @@ function registerSubcommand(feedbackCmd, type) {
|
|
|
98
176
|
company: opts.company,
|
|
99
177
|
token,
|
|
100
178
|
screenshots,
|
|
179
|
+
includeLogs: opts.logs !== false,
|
|
180
|
+
logBundle,
|
|
101
181
|
});
|
|
102
182
|
console.log(`Submitted: ${result.id}`);
|
|
103
183
|
}
|
package/dist/commands/signals.js
CHANGED
|
@@ -101,6 +101,12 @@ async function runList(options) {
|
|
|
101
101
|
const format = options.format ?? defaultListFormat();
|
|
102
102
|
const accessToken = await resolveAccessToken();
|
|
103
103
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
104
|
+
// HQ-59: every company (cmp_) read below rides the presign VaultClient
|
|
105
|
+
// (`usePresign` keys on the vault client + cmp_ prefix), so tell entity
|
|
106
|
+
// resolution to skip the company `POST /sts/vend` — the vended creds were
|
|
107
|
+
// never consumed here, but each call re-entered the packed FILE_ACL fit
|
|
108
|
+
// ladder (policyTruncated noise). Personal vaults still vend-self.
|
|
109
|
+
vaultConfig.companyVaultUsesPresign = true;
|
|
104
110
|
const entity = await resolveEntity({ slug, vaultConfig });
|
|
105
111
|
const result = await listSignals({
|
|
106
112
|
entity,
|
|
@@ -134,6 +140,12 @@ async function runGet(options) {
|
|
|
134
140
|
const format = options.format ?? defaultGetFormat();
|
|
135
141
|
const accessToken = await resolveAccessToken();
|
|
136
142
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
143
|
+
// HQ-59: every company (cmp_) read below rides the presign VaultClient
|
|
144
|
+
// (`usePresign` keys on the vault client + cmp_ prefix), so tell entity
|
|
145
|
+
// resolution to skip the company `POST /sts/vend` — the vended creds were
|
|
146
|
+
// never consumed here, but each call re-entered the packed FILE_ACL fit
|
|
147
|
+
// ladder (policyTruncated noise). Personal vaults still vend-self.
|
|
148
|
+
vaultConfig.companyVaultUsesPresign = true;
|
|
137
149
|
const entity = await resolveEntity({ slug, vaultConfig });
|
|
138
150
|
const doc = await getSignal({
|
|
139
151
|
entity,
|
package/dist/commands/sources.js
CHANGED
|
@@ -109,6 +109,12 @@ async function runList(options) {
|
|
|
109
109
|
const format = options.format ?? defaultListFormat();
|
|
110
110
|
const accessToken = await resolveAccessToken();
|
|
111
111
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
112
|
+
// HQ-59: every company (cmp_) read below rides the presign VaultClient
|
|
113
|
+
// (`usePresign` keys on the vault client + cmp_ prefix), so tell entity
|
|
114
|
+
// resolution to skip the company `POST /sts/vend` — the vended creds were
|
|
115
|
+
// never consumed here, but each call re-entered the packed FILE_ACL fit
|
|
116
|
+
// ladder (policyTruncated noise). Personal vaults still vend-self.
|
|
117
|
+
vaultConfig.companyVaultUsesPresign = true;
|
|
112
118
|
const entity = await resolveEntity({ slug, vaultConfig });
|
|
113
119
|
const result = await listSources({
|
|
114
120
|
entity,
|
|
@@ -142,6 +148,12 @@ async function runGet(options) {
|
|
|
142
148
|
const format = options.format ?? defaultGetFormat();
|
|
143
149
|
const accessToken = await resolveAccessToken();
|
|
144
150
|
const vaultConfig = buildVaultConfig(accessToken);
|
|
151
|
+
// HQ-59: every company (cmp_) read below rides the presign VaultClient
|
|
152
|
+
// (`usePresign` keys on the vault client + cmp_ prefix), so tell entity
|
|
153
|
+
// resolution to skip the company `POST /sts/vend` — the vended creds were
|
|
154
|
+
// never consumed here, but each call re-entered the packed FILE_ACL fit
|
|
155
|
+
// ladder (policyTruncated noise). Personal vaults still vend-self.
|
|
156
|
+
vaultConfig.companyVaultUsesPresign = true;
|
|
145
157
|
const entity = await resolveEntity({ slug, vaultConfig });
|
|
146
158
|
const doc = await getSource({
|
|
147
159
|
entity,
|
package/dist/commands/whoami.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* hq whoami — displays current user or 'not logged in'
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { loadCachedTokens, isExpiring,
|
|
5
|
+
import { loadCachedTokens, isExpiring, loadMachineCreds, } from '@indigoai-us/hq-cloud';
|
|
6
6
|
import { peekIdToken as decodeIdToken } from "../utils/id-token.js";
|
|
7
|
+
import { resolveCognitoTokenSource } from "../utils/cognito-session.js";
|
|
7
8
|
function peekIdToken(idToken) {
|
|
8
9
|
const decoded = decodeIdToken(idToken);
|
|
9
10
|
return {
|
|
@@ -23,7 +24,10 @@ export function registerWhoamiCommand(program) {
|
|
|
23
24
|
.description('Show the currently authenticated user')
|
|
24
25
|
.action(async () => {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
// Effective source (same as auth status / ensureCognitoToken): env
|
|
28
|
+
// opt-in, fresh-box default-creds when no usable person session, or
|
|
29
|
+
// after a definitive person-refresh rejection marked the session rejected.
|
|
30
|
+
const machine = resolveCognitoTokenSource() === "machine";
|
|
27
31
|
const cached = loadCachedTokens();
|
|
28
32
|
if (machine) {
|
|
29
33
|
// Machine identities (company agents) mint sessions on demand from
|
|
@@ -34,6 +38,7 @@ export function registerWhoamiCommand(program) {
|
|
|
34
38
|
? peekIdToken(cached.idToken).entityUid
|
|
35
39
|
: undefined;
|
|
36
40
|
console.log(`Machine identity ${username}${entityUid ? ` (agent ${entityUid})` : ''} — sessions mint automatically`);
|
|
41
|
+
console.log(`token source: machine`);
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
44
|
if (!cached) {
|
|
@@ -93,31 +93,64 @@ export declare function machineTokenStateDir(home?: string, env?: NodeJS.Process
|
|
|
93
93
|
export declare function machineTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
94
94
|
/**
|
|
95
95
|
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
97
|
+
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
98
|
+
* default-file fallback when no usable person session exists.
|
|
99
99
|
*/
|
|
100
100
|
export declare function isMachineCredsFileEnvSet(env?: NodeJS.ProcessEnv): boolean;
|
|
101
|
+
/** Default machine-creds path hq-cloud also checks when the env is unset. */
|
|
102
|
+
export declare function defaultMachineCredsFile(home?: string): string;
|
|
101
103
|
export type EnsureCognitoTokenOptions = {
|
|
102
104
|
interactive?: boolean;
|
|
103
105
|
/**
|
|
104
106
|
* Force machine-credential minting (`"machine"`) or the person login cache
|
|
105
|
-
* (`"person"`). When omitted, machine minting is used
|
|
106
|
-
* `HQ_MACHINE_CREDS_FILE` is
|
|
107
|
+
* (`"person"`). When omitted, machine minting is used if
|
|
108
|
+
* `HQ_MACHINE_CREDS_FILE` is set, or (fresh-box fallback) the default
|
|
109
|
+
* `~/.hq-agent/machine-creds.json` is readable and no person session is
|
|
110
|
+
* cached. Explicit `"person"` never uses machine creds.
|
|
107
111
|
*/
|
|
108
112
|
tokenSource?: CognitoTokenSource;
|
|
109
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* On-disk person Cognito cache path for `env` (`HQ_STATE_DIR` or `~/.hq`).
|
|
116
|
+
* Deliberately does not use hq-cloud's `loadCachedTokens()`: after a machine
|
|
117
|
+
* mint in this process, that helper returns in-memory machine tokens when the
|
|
118
|
+
* person file is absent, which would falsely block the fresh-box fallback.
|
|
119
|
+
*/
|
|
120
|
+
export declare function personTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
121
|
+
/**
|
|
122
|
+
* True when a usable person Cognito cache file exists under the effective
|
|
123
|
+
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
124
|
+
* person path even when the default machine-creds file is also on disk
|
|
125
|
+
* (no-flip guarantee).
|
|
126
|
+
*/
|
|
127
|
+
export declare function hasPersonCachedSession(env?: NodeJS.ProcessEnv): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
130
|
+
* cache, and the default machine-creds file is readable. Distinct from
|
|
131
|
+
* explicit `HQ_MACHINE_CREDS_FILE` / `{ tokenSource: "machine" }` opt-in.
|
|
132
|
+
*/
|
|
133
|
+
export declare function isDefaultMachineCredsFallback(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
110
134
|
/**
|
|
111
135
|
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
136
|
+
*
|
|
137
|
+
* Machine path when:
|
|
138
|
+
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
139
|
+
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
140
|
+
* - fresh-box fallback: env unset, no person cache, default
|
|
141
|
+
* `~/.hq-agent/machine-creds.json` readable.
|
|
142
|
+
*
|
|
143
|
+
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
144
|
+
* person cache keeps the person path even when the default creds file exists
|
|
145
|
+
* (do not flip a human's commands onto machine tokens).
|
|
115
146
|
*/
|
|
116
147
|
export declare function wantsMachineCognitoTokens(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
117
148
|
/**
|
|
118
|
-
* Which Cognito token source this process will use for doctor /
|
|
119
|
-
*
|
|
120
|
-
*
|
|
149
|
+
* Which Cognito token source this process will use for doctor / status / whoami.
|
|
150
|
+
* Matches {@link wantsMachineCognitoTokens}: env opt-in, or default-file
|
|
151
|
+
* fallback when no usable person session is cached (absent, or marked rejected
|
|
152
|
+
* after a definitive Cognito refresh refusal). Transient refresh failures do
|
|
153
|
+
* not flip this to machine.
|
|
121
154
|
*/
|
|
122
155
|
export declare function resolveCognitoTokenSource(env?: NodeJS.ProcessEnv): CognitoTokenSource;
|
|
123
156
|
/** Derive actor kind from an ID token's custom:entity* claims (no verify). */
|
|
@@ -143,19 +176,28 @@ export declare function describeCognitoTokenSource(opts?: {
|
|
|
143
176
|
* Run `fn` with `HQ_STATE_DIR` pointed at the machine/daemon token cache so
|
|
144
177
|
* hq-cloud's mint writes never touch the person login file.
|
|
145
178
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
179
|
+
* Always redirects — even when the caller already set `HQ_STATE_DIR` (person
|
|
180
|
+
* cache / tests). Override the machine cache location with
|
|
181
|
+
* `HQ_MACHINE_TOKEN_STATE_DIR` instead. Restores the prior env afterwards.
|
|
148
182
|
*/
|
|
149
183
|
export declare function withMachineTokenStateDir<T>(fn: () => Promise<T>, opts?: {
|
|
150
184
|
home?: string;
|
|
151
185
|
env?: NodeJS.ProcessEnv;
|
|
152
186
|
}): Promise<T>;
|
|
187
|
+
/**
|
|
188
|
+
* True when a person-token refresh failed with a definitive Cognito auth
|
|
189
|
+
* rejection (refresh token invalid/revoked). Transient network/5xx/timeouts
|
|
190
|
+
* must NOT trigger machine-identity fallback.
|
|
191
|
+
*/
|
|
192
|
+
export declare function isDefinitivePersonAuthRejection(err: unknown): boolean;
|
|
153
193
|
/**
|
|
154
194
|
* Return a non-expired Cognito access token, refreshing or browser-logging-in
|
|
155
195
|
* as needed. Person cache lives at ~/.hq/cognito-tokens.json. Machine minting
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
196
|
+
* uses `HQ_MACHINE_CREDS_FILE` / `{ tokenSource: "machine" }`, or the fresh-box
|
|
197
|
+
* fallback when the default creds file is readable and no person session
|
|
198
|
+
* exists (or person refresh fails with a definitive auth rejection).
|
|
199
|
+
* Machine tokens always cache under the dedicated machine/daemon state dir
|
|
200
|
+
* (never the caller's `HQ_STATE_DIR` person cache).
|
|
159
201
|
*
|
|
160
202
|
* Pass `interactive: false` from automated contexts (e.g. the `hq-auth-refresh`
|
|
161
203
|
* bin invoked by the deploy skill) where failing fast is better than opening
|