@indigoai-us/hq-cli 5.103.8 → 5.103.9
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,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.103.9] — 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- The checkpoint stop-gate no longer causes duplicated or hidden replies
|
|
10
|
+
around the end-of-turn checkpoint. The gate now reads the transcript to
|
|
11
|
+
detect whether the agent already delivered its user-facing reply this turn
|
|
12
|
+
and issues the matching instruction: checkpoint-then-full-reply when no
|
|
13
|
+
reply exists yet, or checkpoint-and-end-without-repeating when it does. The
|
|
14
|
+
`hq core checkpoint` reply reminder carries the same conditional contract.
|
|
15
|
+
|
|
5
16
|
## [5.103.8] — 2026-08-19
|
|
6
17
|
|
|
7
18
|
### Fixed
|
|
@@ -236,14 +236,16 @@ set -uo pipefail
|
|
|
236
236
|
| [range(0; $rows | length) | select($rows[.].entry | real_user)] as $user_indexes
|
|
237
237
|
| if ($user_indexes | length) == 0 then error("no real user entry") else
|
|
238
238
|
$user_indexes[-1] as $user_index
|
|
239
|
+
| $rows[($user_index + 1):] as $turn
|
|
239
240
|
| [
|
|
240
|
-
|
|
241
|
-
| .entry
|
|
241
|
+
range(0; $turn | length) as $ti
|
|
242
|
+
| $turn[$ti].entry
|
|
242
243
|
| if .type == "assistant" then
|
|
243
244
|
.message.content?
|
|
244
245
|
| if type == "array" then .[]? else empty end
|
|
245
246
|
| select(.type == "tool_use")
|
|
246
247
|
| {
|
|
248
|
+
i: $ti,
|
|
247
249
|
runtime: "claude",
|
|
248
250
|
id: (.id? // ""),
|
|
249
251
|
name: (.name? // ""),
|
|
@@ -255,6 +257,7 @@ set -uo pipefail
|
|
|
255
257
|
) then
|
|
256
258
|
.payload
|
|
257
259
|
| {
|
|
260
|
+
i: $ti,
|
|
258
261
|
runtime: "codex",
|
|
259
262
|
id: (.call_id? // .id? // ""),
|
|
260
263
|
name: (.name? // ""),
|
|
@@ -264,6 +267,30 @@ set -uo pipefail
|
|
|
264
267
|
end
|
|
265
268
|
] as $tools
|
|
266
269
|
| (if ($tools | length) > 0 then $tools[-1] else {} end) as $last_tool
|
|
270
|
+
# A user-visible assistant reply strictly AFTER the turn-s last tool call
|
|
271
|
+
# means the user has already been answered, so the block reason must
|
|
272
|
+
# forbid a restatement instead of demanding one. Text earlier in the turn
|
|
273
|
+
# (status notes between tool calls) does not count as the reply.
|
|
274
|
+
| (if ($tools | length) > 0 then $tools[-1].i else -1 end) as $last_tool_i
|
|
275
|
+
| ([
|
|
276
|
+
$turn[($last_tool_i + 1):][]
|
|
277
|
+
| .entry
|
|
278
|
+
| if .type == "assistant" then
|
|
279
|
+
.message.content?
|
|
280
|
+
| if type == "string" then .
|
|
281
|
+
elif type == "array" then ([.[]? | select(.type == "text") | (.text? // "")] | join(""))
|
|
282
|
+
else ""
|
|
283
|
+
end
|
|
284
|
+
elif (
|
|
285
|
+
.type == "response_item"
|
|
286
|
+
and .payload.type? == "message"
|
|
287
|
+
and .payload.role? == "assistant"
|
|
288
|
+
and ((.payload.content? | type) == "array")
|
|
289
|
+
) then
|
|
290
|
+
([.payload.content[]? | (.text? // "")] | join(""))
|
|
291
|
+
else ""
|
|
292
|
+
end
|
|
293
|
+
] | join("") | test("\\S")) as $replied
|
|
267
294
|
| ([$tools[] | select((.command | checkpoint_command) | not)] | length) as $work_tool_count
|
|
268
295
|
| ([
|
|
269
296
|
$rows[]
|
|
@@ -280,14 +307,19 @@ set -uo pipefail
|
|
|
280
307
|
) then
|
|
281
308
|
if $last_tool.runtime == "claude" and ($last_result_error | not) then "1"
|
|
282
309
|
elif $last_tool.runtime == "codex" then "stamp"
|
|
283
|
-
else "0"
|
|
310
|
+
else (if $replied then "0-replied" else "0-unreplied" end)
|
|
284
311
|
end
|
|
285
312
|
elif $work_tool_count == 0 then "idle"
|
|
286
|
-
else "0"
|
|
313
|
+
else (if $replied then "0-replied" else "0-unreplied" end)
|
|
287
314
|
end
|
|
288
315
|
end
|
|
289
316
|
' 2>/dev/null)" || exit 0
|
|
290
317
|
satisfied="$parsed"
|
|
318
|
+
replied=0
|
|
319
|
+
case "$satisfied" in
|
|
320
|
+
0-replied) satisfied=0; replied=1 ;;
|
|
321
|
+
0-unreplied) satisfied=0 ;;
|
|
322
|
+
esac
|
|
291
323
|
case "$satisfied" in
|
|
292
324
|
0|1|stamp|idle) ;;
|
|
293
325
|
*) exit 0 ;;
|
|
@@ -349,7 +381,19 @@ set -uo pipefail
|
|
|
349
381
|
|
|
350
382
|
# Built with printf rather than concatenation so the session id can appear in
|
|
351
383
|
# both commands without re-splitting the message into fragments.
|
|
352
|
-
|
|
384
|
+
#
|
|
385
|
+
# Two variants, chosen from the transcript rather than left to the agent's
|
|
386
|
+
# judgment: the historical single instruction either demanded a full
|
|
387
|
+
# restatement (double-messaging hosts that render pre-tool text — the CLI and
|
|
388
|
+
# the desktop app both do) or forbade post-checkpoint text (hiding the reply
|
|
389
|
+
# when the agent had not yet written one). The transcript already tells us
|
|
390
|
+
# which case we are in, so say exactly one thing.
|
|
391
|
+
flags_spec=' 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'
|
|
392
|
+
if [ "$replied" = 1 ]; then
|
|
393
|
+
reason="$(printf 'This turn changed something, so it needs an end-of-turn checkpoint before it can end.\n\nYour user-facing reply is ALREADY delivered — the message you just wrote is visible to the user. Do NOT send it again, in full or summarized: repeating it double-messages the user, which is exactly the bug this gate guards against.\n\nTHE SIBLING (a background maintenance agent) reads only the checkpoint payload, never your chat reply — anything it needs must go into the flags.\n\nRun the checkpoint now as the FINAL action of the turn and end the turn immediately after it, adding no further text:\n\n'"$flags_spec" "$session_id" "$session_id")"
|
|
394
|
+
else
|
|
395
|
+
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. You have not sent one yet this turn — everything you owe them (results, links, answers, status, decisions) must go into it. The checkpoint is invisible to them and 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: run the checkpoint FIRST, then deliver your complete user-facing reply as the FINAL text of the turn — final-position text is the one placement every host renders in full. Every link, URL, instruction, command, and decision the user needs must appear in that final message.\n\n'"$flags_spec" "$session_id" "$session_id")"
|
|
396
|
+
fi
|
|
353
397
|
|
|
354
398
|
# Codex surfaces a blocked Stop reason as a synthetic user prompt. Preserve
|
|
355
399
|
# the actionable instruction out-of-band, then use the stable marker covered
|
|
@@ -14,11 +14,14 @@ type SpawnableBackend = Exclude<Backend, "none">;
|
|
|
14
14
|
* treating the checkpoint call as the end of the turn, leaving their actual
|
|
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
|
-
* calling agent) to force the user-facing reply. Deliberately conditional
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* calling agent) to force the user-facing reply. Deliberately conditional on
|
|
18
|
+
* whether the reply was already delivered this turn: an unconditional
|
|
19
|
+
* "restate in full" made agents repeat a reply the user had already seen
|
|
20
|
+
* (both the CLI and the desktop app render pre-tool-call text), while an
|
|
21
|
+
* unconditional "no commentary after" hid the reply from agents that had not
|
|
22
|
+
* yet written one. The Stop gate makes the same distinction from the
|
|
23
|
+
* transcript; this line covers proactive checkpoints the gate never sees.
|
|
24
|
+
* Mirrors the hq-core policies
|
|
22
25
|
* `checkpoint-is-bookkeeping-not-user-communication` and
|
|
23
26
|
* `checkpoint-is-not-the-user-report`.
|
|
24
27
|
*/
|
|
@@ -45,20 +45,23 @@ class CheckpointUsageError extends Error {
|
|
|
45
45
|
* treating the checkpoint call as the end of the turn, leaving their actual
|
|
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
|
-
* calling agent) to force the user-facing reply. Deliberately conditional
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* calling agent) to force the user-facing reply. Deliberately conditional on
|
|
49
|
+
* whether the reply was already delivered this turn: an unconditional
|
|
50
|
+
* "restate in full" made agents repeat a reply the user had already seen
|
|
51
|
+
* (both the CLI and the desktop app render pre-tool-call text), while an
|
|
52
|
+
* unconditional "no commentary after" hid the reply from agents that had not
|
|
53
|
+
* yet written one. The Stop gate makes the same distinction from the
|
|
54
|
+
* transcript; this line covers proactive checkpoints the gate never sees.
|
|
55
|
+
* Mirrors the hq-core policies
|
|
53
56
|
* `checkpoint-is-bookkeeping-not-user-communication` and
|
|
54
57
|
* `checkpoint-is-not-the-user-report`.
|
|
55
58
|
*/
|
|
56
59
|
export const CHECKPOINT_REPLY_REMINDER = "checkpoint: REMINDER — this checkpoint is invisible bookkeeping; the user never sees it " +
|
|
57
|
-
"and it does NOT count as your reply.
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
60
|
+
"and it does NOT count as your reply. If this turn's substance (results, findings, " +
|
|
61
|
+
"decisions, state changes, anything awaiting their input) has NOT yet been delivered to " +
|
|
62
|
+
"the user, write your user-facing reply now as the FINAL text of the turn. If you " +
|
|
63
|
+
"already delivered it earlier this turn, end the turn now WITHOUT repeating it — the " +
|
|
64
|
+
"user has already seen that message, and repeating it double-messages them.";
|
|
62
65
|
function printResult(line) {
|
|
63
66
|
process.stdout.write(`${line}\n`);
|
|
64
67
|
}
|