@indigoai-us/hq-cli 5.103.6 → 5.103.8
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,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.103.8] — 2026-08-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The checkpoint stop-gate can no longer re-prompt a session forever. When the
|
|
10
|
+
checkpoint command keeps failing (e.g. a broken or mid-self-update `hq`
|
|
11
|
+
binary), the gate now fails open after 3 consecutive blocks per session and
|
|
12
|
+
re-arms once a checkpoint succeeds — a broken CLI cannot loop a session
|
|
13
|
+
indefinitely.
|
|
14
|
+
- A failed startup self-update now points at `hq rescue` in addition to the
|
|
15
|
+
manual manager command. A corrupt install layout fails the manual retry the
|
|
16
|
+
same way; rescue is the path that can actually rebuild the install.
|
|
17
|
+
|
|
18
|
+
## [5.103.7] — 2026-08-19
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- The checkpoint stop-gate and `hq core checkpoint` REMINDER now order the turn
|
|
23
|
+
checkpoint-first / user-reply-last. App-runtime Claude Code hosts collapse
|
|
24
|
+
assistant text that precedes a tool call, so the old "reply first, checkpoint
|
|
25
|
+
as final action" contract silently hid the substantive reply from the user.
|
|
26
|
+
|
|
5
27
|
## [5.103.6] — 2026-08-19
|
|
6
28
|
|
|
7
29
|
## [5.103.5] — 2026-08-19
|
|
@@ -120,7 +120,7 @@ set -uo pipefail
|
|
|
120
120
|
*) [ -d "$gate_root/companies/$bound_co" ] && gate_bound=1 ;;
|
|
121
121
|
esac
|
|
122
122
|
if [ "$gate_bound" = 0 ]; then
|
|
123
|
-
company_reason="$(printf 'This session has not declared its scope, and a scope is required before the turn can end. Decide where this work belongs, then bind the session
|
|
123
|
+
company_reason="$(printf 'This session has not declared its scope, and a scope is required before the turn can end. Decide where this work belongs, then bind the session and finish the turn with your user-facing reply as the final text after the command output:\n\n bash %s/core/scripts/hq-session.sh --session-id %s set company_slug <company-slug>\n\nUse a real tenant slug from companies/manifest.yaml — the company this session is actually working in, never an invented one. If this session does no company-scoped work, bind it to the reserved personal scope instead:\n\n bash %s/core/scripts/hq-session.sh --session-id %s set company_slug personal\n\nAn operator can also disable this requirement for the run by exporting HQ_CHECKPOINT_GATE=0.' "$gate_root" "$gate_session_id" "$gate_root" "$gate_session_id")"
|
|
124
124
|
printf '{"decision":"block","reason":%s}\n' "$(printf '%s' "$company_reason" | hq_json_encode)"
|
|
125
125
|
exit 0
|
|
126
126
|
fi
|
|
@@ -322,12 +322,34 @@ set -uo pipefail
|
|
|
322
322
|
if [ "$runtime" = "codex" ]; then
|
|
323
323
|
rm -f "$state_dir/codex-checkpoint-reprompt-$session_key" 2>/dev/null || true
|
|
324
324
|
fi
|
|
325
|
+
rm -f "$state_dir/checkpoint-block-count-$session_key" 2>/dev/null || true
|
|
325
326
|
exit 0
|
|
326
327
|
fi
|
|
327
328
|
|
|
329
|
+
# Loop guard: a session whose checkpoint command keeps FAILING (a broken or
|
|
330
|
+
# mid-self-update `hq` binary) would otherwise be re-prompted on every Stop,
|
|
331
|
+
# forever — block → retry checkpoint → error → block again. Cap consecutive
|
|
332
|
+
# blocks per session; past the cap, fail open per this file's
|
|
333
|
+
# never-strand-a-session doctrine. Any successful checkpoint or idle turn
|
|
334
|
+
# resets the counter above.
|
|
335
|
+
block_count_file="$state_dir/checkpoint-block-count-$session_key"
|
|
336
|
+
block_count="$(tr -d '\r\n' <"$block_count_file" 2>/dev/null || true)"
|
|
337
|
+
case "$block_count" in
|
|
338
|
+
''|*[!0-9]*) block_count=0 ;;
|
|
339
|
+
esac
|
|
340
|
+
if [ "$block_count" -ge 3 ]; then
|
|
341
|
+
exit 0
|
|
342
|
+
fi
|
|
343
|
+
block_count_tmp="$block_count_file.$$"
|
|
344
|
+
if (umask 077 && printf '%s' "$((block_count + 1))" >"$block_count_tmp" && mv -f "$block_count_tmp" "$block_count_file"); then
|
|
345
|
+
:
|
|
346
|
+
else
|
|
347
|
+
rm -f "$block_count_tmp" 2>/dev/null || true
|
|
348
|
+
fi
|
|
349
|
+
|
|
328
350
|
# Built with printf rather than concatenation so the session id can appear in
|
|
329
351
|
# both commands without re-splitting the message into fragments.
|
|
330
|
-
reason="$(printf 'This turn changed something, so it needs an end-of-turn checkpoint. Two different audiences are involved — do not conflate them:\n\n1. THE USER reads your normal chat reply. If you owe them anything — a result, a link, an answer, a status — say it in the reply as usual. The checkpoint is invisible to them and is NOT a message to them; running it does not count as having replied.\n2. THE SIBLING (a background maintenance agent) reads the checkpoint payload. It never sees your chat reply, so anything it needs must go into the flags.\n\
|
|
352
|
+
reason="$(printf 'This turn changed something, so it needs an end-of-turn checkpoint. Two different audiences are involved — do not conflate them:\n\n1. THE USER reads your normal chat reply. If you owe them anything — a result, a link, an answer, a status — say it in the reply as usual. The checkpoint is invisible to them and is NOT a message to them; running it does not count as having replied.\n2. THE SIBLING (a background maintenance agent) reads the checkpoint payload. It never sees your chat reply, so anything it needs must go into the flags.\n\nORDER (this app folds any text that precedes a tool call into a collapsed sub-message — only text AFTER the last tool call renders in full): run the checkpoint FIRST, then deliver your COMPLETE user-facing reply as the final text of the turn. Every link, URL, instruction, command, and decision the user needs MUST appear in that final post-checkpoint message, restated in full even if you already wrote it earlier in the turn — earlier text is collapsed and the user will not see it.\n\n hq core checkpoint --session-id %s --trigger stop-gate --summary "<what changed, in one line>" [--file <path>] [--decision "<choice and why>"] [--learning "<reusable rule>"] [--next "<outstanding step>"]\n\nOnly --summary is required, and the repeatable flags are what the sibling uses to enrich the record, distil policies and update the indexes — a bare summary gives it almost nothing to work with. Write them as machine record, not prose for the user, and pass each one that genuinely applies:\n --file every path you created or modified this turn\n --decision a choice you made that a reader would otherwise have to reverse-engineer\n --learning a rule that changes how someone acts next time, not a restatement of what just happened\n --next work that is genuinely still outstanding\nOmit a flag rather than padding it: an empty or invented learning is worse than none.\n\nIf this turn only read or inspected things and changed no state, the correct call instead is:\n\n hq core checkpoint --session-id %s --idle' "$session_id" "$session_id")"
|
|
331
353
|
|
|
332
354
|
# Codex surfaces a blocked Stop reason as a synthetic user prompt. Preserve
|
|
333
355
|
# the actionable instruction out-of-band, then use the stable marker covered
|
|
@@ -15,9 +15,10 @@ type SpawnableBackend = Exclude<Backend, "none">;
|
|
|
15
15
|
* findings only in the checkpoint payload — which the user never sees. This
|
|
16
16
|
* line rides the command output (the one channel guaranteed to reach the
|
|
17
17
|
* calling agent) to force the user-facing reply. Deliberately conditional so
|
|
18
|
-
* it composes with the Stop-gate prompt's "
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* it composes with the Stop-gate prompt's "checkpoint first, reply last"
|
|
19
|
+
* ordering: some hosts (the desktop app runtime) do not reliably display
|
|
20
|
+
* assistant text that precedes a tool call, so the reply must come after
|
|
21
|
+
* the checkpoint output as the final text of the turn. Mirrors the hq-core policies
|
|
21
22
|
* `checkpoint-is-bookkeeping-not-user-communication` and
|
|
22
23
|
* `checkpoint-is-not-the-user-report`.
|
|
23
24
|
*/
|
|
@@ -46,17 +46,19 @@ class CheckpointUsageError extends Error {
|
|
|
46
46
|
* findings only in the checkpoint payload — which the user never sees. This
|
|
47
47
|
* line rides the command output (the one channel guaranteed to reach the
|
|
48
48
|
* calling agent) to force the user-facing reply. Deliberately conditional so
|
|
49
|
-
* it composes with the Stop-gate prompt's "
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* it composes with the Stop-gate prompt's "checkpoint first, reply last"
|
|
50
|
+
* ordering: some hosts (the desktop app runtime) do not reliably display
|
|
51
|
+
* assistant text that precedes a tool call, so the reply must come after
|
|
52
|
+
* the checkpoint output as the final text of the turn. Mirrors the hq-core policies
|
|
52
53
|
* `checkpoint-is-bookkeeping-not-user-communication` and
|
|
53
54
|
* `checkpoint-is-not-the-user-report`.
|
|
54
55
|
*/
|
|
55
56
|
export const CHECKPOINT_REPLY_REMINDER = "checkpoint: REMINDER — this checkpoint is invisible bookkeeping; the user never sees it " +
|
|
56
|
-
"and it does NOT count as your reply.
|
|
57
|
-
"everything of substance from this turn,
|
|
58
|
-
"
|
|
59
|
-
"
|
|
57
|
+
"and it does NOT count as your reply. Now write your user-facing reply as the FINAL text " +
|
|
58
|
+
"of the turn — everything of substance from this turn (results, findings, decisions, " +
|
|
59
|
+
"state changes, anything awaiting their input) — even if you already wrote it earlier: " +
|
|
60
|
+
"assistant text that precedes a tool call is not reliably shown to the user on all " +
|
|
61
|
+
"hosts. Then end the turn.";
|
|
60
62
|
function printResult(line) {
|
|
61
63
|
process.stdout.write(`${line}\n`);
|
|
62
64
|
}
|
|
@@ -288,6 +288,14 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
288
288
|
console.error(chalk.yellow(`⚠ hq-cli ${latest} is available but the update failed` +
|
|
289
289
|
`${result.detail ? `: ${result.detail}` : ""}`));
|
|
290
290
|
console.error(chalk.dim(` Try manually: ${plan.cmd} ${plan.args.join(" ")}`));
|
|
291
|
+
// A manager-level failure often means the install layout itself is broken
|
|
292
|
+
// (e.g. a hand-rolled pnpm store nested inside the app's bin dir). The
|
|
293
|
+
// manual retry above hits the same layout and fails the same way; point at
|
|
294
|
+
// the recovery path that can actually rebuild the install — except from the
|
|
295
|
+
// rescue itself, which would just recurse.
|
|
296
|
+
if (flavor.noun !== "rescue") {
|
|
297
|
+
console.error(chalk.dim(` Or repair the install: hq rescue`));
|
|
298
|
+
}
|
|
291
299
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
292
300
|
return { action: "update-failed", latest };
|
|
293
301
|
}
|