@indigoai-us/hq-cli 5.342.16 → 5.342.18
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 +8 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +2 -2
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +1 -1
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +1 -1
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +1 -1
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +6 -2
- package/dist/commands/agents.d.ts +1 -6
- package/dist/commands/agents.js +17 -6
- package/dist/commands/lanes-message.js +2 -4
- package/dist/commands/lanes-shared.d.ts +2 -2
- package/dist/commands/lanes.js +24 -15
- package/dist/lib/index-render/index.js +4 -1
- package/dist/lib/index-render/social-drafts.js +2 -2
- package/dist/lib/lanes/keep-alive-audit.js +0 -2
- package/dist/lib/lanes/reconcile.js +23 -1
- package/dist/lib/lanes/types.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.342.18] — 2026-10-04
|
|
6
|
+
- Preserve confirmed remote lane-message responses for in-process monitor delivery.
|
|
7
|
+
- Use native TypeScript renderers for `hq core rebuild-index` commands.
|
|
8
|
+
|
|
9
|
+
## [5.342.17] — 2026-10-04
|
|
10
|
+
- Show agent display names in the agents list and accept them when selecting an agent.
|
|
11
|
+
- Resume due blocked keep-alive for story-less standing lead lanes, record no-open-story skips in lane history, and report the latest blocked skip in keep-alive audit.
|
|
12
|
+
|
|
5
13
|
## [5.342.16] — 2026-10-04
|
|
6
14
|
- Keep resident command leases until monitor, agent, OAuth, bot, daemon and lane-watch runtime modules and their transitive Cognito, first-sync, hq-cloud and local-file dependencies are preloaded.
|
|
7
15
|
- CI now runs the core utility parity assertions on Ubuntu, macOS, and Windows.
|
|
@@ -44,7 +44,7 @@ describe_item() {
|
|
|
44
44
|
n=$(find "$path" -mindepth 1 -maxdepth 1 ! -name '.*' 2>/dev/null | wc -l | tr -d ' ')
|
|
45
45
|
local h=""
|
|
46
46
|
if [[ -f "$path/README.md" ]]; then
|
|
47
|
-
h=$(awk '/^# / { sub(/^# +/, ""); print; exit }' "$path/README.md" 2>/dev/null || true)
|
|
47
|
+
h=$(awk '/^# / { sub(/^# +/, ""); sub(/\r$/, ""); print; exit }' "$path/README.md" 2>/dev/null || true)
|
|
48
48
|
fi
|
|
49
49
|
if [[ -n "$h" ]]; then
|
|
50
50
|
echo "${h} (${n} item(s))"
|
|
@@ -56,7 +56,7 @@ describe_item() {
|
|
|
56
56
|
case "$name" in
|
|
57
57
|
*.md)
|
|
58
58
|
local h
|
|
59
|
-
h=$(awk '/^# / { sub(/^# +/, ""); print; exit }' "$path" 2>/dev/null || true)
|
|
59
|
+
h=$(awk '/^# / { sub(/^# +/, ""); sub(/\r$/, ""); print; exit }' "$path" 2>/dev/null || true)
|
|
60
60
|
[[ -z "$h" ]] && h="$name"
|
|
61
61
|
echo "$h"
|
|
62
62
|
;;
|
|
@@ -25,7 +25,7 @@ trunc() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
heading_md() {
|
|
28
|
-
awk '/^# / { sub(/^# +/, ""); print; exit }' "$1" 2>/dev/null || true
|
|
28
|
+
awk '/^# / { sub(/^# +/, ""); sub(/\r$/, ""); print; exit }' "$1" 2>/dev/null || true
|
|
29
29
|
}
|
|
30
30
|
title_html() {
|
|
31
31
|
awk 'BEGIN{IGNORECASE=1} /<title>/ { sub(/.*<title>/, ""); sub(/<\/title>.*/, ""); print; exit }' "$1" 2>/dev/null || true
|
|
@@ -26,8 +26,12 @@ worker_desc() {
|
|
|
26
26
|
if command -v yq >/dev/null 2>&1; then
|
|
27
27
|
yq -r '.worker.description // .worker.bio.role // ""' "$yaml" 2>/dev/null || echo ""
|
|
28
28
|
else
|
|
29
|
-
# Fallback:
|
|
30
|
-
awk -F: '
|
|
29
|
+
# Fallback for machines without yq: prefer description, then bio.role.
|
|
30
|
+
awk -F: '
|
|
31
|
+
/^[[:space:]]+description:/ { sub(/^[^:]+:[[:space:]]*/, ""); gsub(/^["\x27]|["\x27]$/, ""); print; found=1; exit }
|
|
32
|
+
/^[[:space:]]+role:/ { role=$0; sub(/^[^:]+:[[:space:]]*/, "", role); gsub(/^["\x27]|["\x27]$/, "", role) }
|
|
33
|
+
END { if (!found && role != "") print role }
|
|
34
|
+
' "$yaml" 2>/dev/null || echo ""
|
|
31
35
|
fi
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -127,6 +127,7 @@ export interface CompanyAgentView {
|
|
|
127
127
|
codexModel?: string;
|
|
128
128
|
codexReasoningEffort?: string;
|
|
129
129
|
codexServiceTier?: string;
|
|
130
|
+
displayName?: string;
|
|
130
131
|
profile?: {
|
|
131
132
|
displayName?: string;
|
|
132
133
|
title?: string;
|
|
@@ -368,12 +369,6 @@ export interface DmThreadMessage {
|
|
|
368
369
|
/** "out" = sent by the caller; "in" = received (the agent's reply). */
|
|
369
370
|
direction: "in" | "out";
|
|
370
371
|
}
|
|
371
|
-
/**
|
|
372
|
-
* Resolve an agent reference to its `agt_` uid. An `agt_…` value is used
|
|
373
|
-
* directly; anything else is treated as a slug or display name and resolved
|
|
374
|
-
* against the company's roster (requires `--company` or a single active
|
|
375
|
-
* membership).
|
|
376
|
-
*/
|
|
377
372
|
export declare function resolveAgentUid(token: string, ref: string, companySlug: string | undefined): Promise<string>;
|
|
378
373
|
/**
|
|
379
374
|
* Fail-closed local guard for the off-box `hq agents jobs` operator commands.
|
package/dist/commands/agents.js
CHANGED
|
@@ -996,15 +996,26 @@ function catalogUnavailableMessage() {
|
|
|
996
996
|
* against the company's roster (requires `--company` or a single active
|
|
997
997
|
* membership).
|
|
998
998
|
*/
|
|
999
|
+
function effectiveAgentDisplayName(agent) {
|
|
1000
|
+
return agent.profile?.displayName || agent.displayName || agent.name || "";
|
|
1001
|
+
}
|
|
999
1002
|
export async function resolveAgentUid(token, ref, companySlug) {
|
|
1000
1003
|
if (AGENT_UID_PATTERN.test(ref.trim()))
|
|
1001
1004
|
return ref.trim();
|
|
1002
1005
|
const companyUid = await getCompanyUid(token, companySlug);
|
|
1003
1006
|
const roster = await listAgents(token, companyUid);
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1007
|
+
const trimmedRef = ref.trim();
|
|
1008
|
+
const needle = trimmedRef.toLowerCase();
|
|
1009
|
+
const exactMatch = roster.find((agent) => agent.slug?.toLowerCase() === needle) ??
|
|
1010
|
+
roster.find((agent) => agent.name?.toLowerCase() === needle) ??
|
|
1011
|
+
roster.find((agent) => agent.uid === trimmedRef);
|
|
1012
|
+
if (exactMatch)
|
|
1013
|
+
return exactMatch.uid;
|
|
1014
|
+
const displayNameMatches = roster.filter((agent) => effectiveAgentDisplayName(agent).toLowerCase() === needle);
|
|
1015
|
+
if (displayNameMatches.length > 1) {
|
|
1016
|
+
throw new AgentsHttpError(409, `More than one agent matches "${ref}" in this company. Use an agent slug or agt_ UID.`);
|
|
1017
|
+
}
|
|
1018
|
+
const match = displayNameMatches[0];
|
|
1008
1019
|
if (!match) {
|
|
1009
1020
|
throw new AgentsHttpError(404, `No agent named "${ref}" in this company. Run \`hq agents list\` to see slugs/uids, ` +
|
|
1010
1021
|
`or pass the agt_ uid directly.`);
|
|
@@ -1697,7 +1708,7 @@ export function registerAgentsCommand(program) {
|
|
|
1697
1708
|
console.log(chalk.gray("No agents found for this company."));
|
|
1698
1709
|
return;
|
|
1699
1710
|
}
|
|
1700
|
-
const nameW = Math.max(4, ...roster.map((
|
|
1711
|
+
const nameW = Math.max(4, ...roster.map((agent) => effectiveAgentDisplayName(agent).length));
|
|
1701
1712
|
const slugW = Math.max(4, ...roster.map((a) => (a.slug ?? "").length));
|
|
1702
1713
|
const statusW = Math.max(6, ...roster.map((a) => rosterStatusLabel(a).length));
|
|
1703
1714
|
const modelW = Math.max(5, ...roster.map((a) => (a.codexModel ?? "").length));
|
|
@@ -1710,7 +1721,7 @@ export function registerAgentsCommand(program) {
|
|
|
1710
1721
|
].join(" ")));
|
|
1711
1722
|
for (const a of roster) {
|
|
1712
1723
|
console.log([
|
|
1713
|
-
(a
|
|
1724
|
+
effectiveAgentDisplayName(a).padEnd(nameW),
|
|
1714
1725
|
(a.slug ?? "").padEnd(slugW),
|
|
1715
1726
|
rosterStatusLabel(a).padEnd(statusW),
|
|
1716
1727
|
(a.codexModel ?? "").padEnd(modelW),
|
|
@@ -309,8 +309,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
309
309
|
throw err;
|
|
310
310
|
}
|
|
311
311
|
const text = readMessageText(requestedLaneId);
|
|
312
|
-
await dispatchPrimaryLaneMessage(ctx, requestedLaneId, { ...opts, text });
|
|
313
|
-
return;
|
|
312
|
+
return await dispatchPrimaryLaneMessage(ctx, requestedLaneId, { ...opts, text });
|
|
314
313
|
}
|
|
315
314
|
laneId = resolution.lane.lane_id;
|
|
316
315
|
let text = readMessageText(laneId);
|
|
@@ -336,7 +335,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
336
335
|
callerMatchesSessionSenior(ctx, sessionSeniorId)
|
|
337
336
|
? sessionSeniorId
|
|
338
337
|
: undefined;
|
|
339
|
-
await dispatchOutpostLaneCommand(ctx, lane, "message", {
|
|
338
|
+
return await dispatchOutpostLaneCommand(ctx, lane, "message", {
|
|
340
339
|
...opts,
|
|
341
340
|
text,
|
|
342
341
|
...(supersededMessageCallerId === undefined ? {} : { supersededMessageCallerId }),
|
|
@@ -349,7 +348,6 @@ export function createLaneMessageHandlers(deps) {
|
|
|
349
348
|
afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "message", fallbackAt),
|
|
350
349
|
}),
|
|
351
350
|
});
|
|
352
|
-
return;
|
|
353
351
|
}
|
|
354
352
|
assertLaneRunsHereForOperatorMutation(ctx, lane, "message");
|
|
355
353
|
const projectId = opts.project?.trim() || lane.project_id;
|
|
@@ -179,8 +179,8 @@ export interface LaneMessageHandlerDeps {
|
|
|
179
179
|
alreadyQueued?: boolean;
|
|
180
180
|
}): Promise<AdmissionResult>;
|
|
181
181
|
casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
182
|
-
dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "message" | "resume", options: LaneMessageOptions | LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<
|
|
183
|
-
dispatchPrimaryLaneMessage(ctx: Ctx, laneId: string, options: LaneMessageOptions): Promise<
|
|
182
|
+
dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "message" | "resume", options: LaneMessageOptions | LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<LaneMessageResponse | undefined>;
|
|
183
|
+
dispatchPrimaryLaneMessage(ctx: Ctx, laneId: string, options: LaneMessageOptions): Promise<LaneMessageResponse | undefined>;
|
|
184
184
|
drainHookStatusFor(ctx: Ctx, lane: LaneRecord): {
|
|
185
185
|
registered: boolean;
|
|
186
186
|
kind: string;
|
package/dist/commands/lanes.js
CHANGED
|
@@ -4516,7 +4516,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4516
4516
|
const ownerCancellationFailures = Array.isArray(remote.question_cancellation_failures)
|
|
4517
4517
|
? remote.question_cancellation_failures
|
|
4518
4518
|
: [];
|
|
4519
|
-
|
|
4519
|
+
const response = {
|
|
4520
4520
|
...remote,
|
|
4521
4521
|
lane_id: ownerLane.lane_id,
|
|
4522
4522
|
lane: ownerLane,
|
|
@@ -4534,8 +4534,10 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4534
4534
|
outpost_command_id: dispatched.commandId,
|
|
4535
4535
|
outpost_transport: "outposts_exec",
|
|
4536
4536
|
delivered_to: { host: outpostId, lane_id: ownerLane.lane_id, mechanism: "outposts_exec" },
|
|
4537
|
-
}
|
|
4538
|
-
|
|
4537
|
+
};
|
|
4538
|
+
if (!("silent" in options) || !options.silent)
|
|
4539
|
+
printJson(response);
|
|
4540
|
+
return response;
|
|
4539
4541
|
}
|
|
4540
4542
|
if (action === "approve" || action === "reparent" || action === "resume") {
|
|
4541
4543
|
const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
|
|
@@ -4578,7 +4580,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4578
4580
|
});
|
|
4579
4581
|
return;
|
|
4580
4582
|
}
|
|
4581
|
-
|
|
4583
|
+
const response = {
|
|
4582
4584
|
...remote,
|
|
4583
4585
|
outpost_id: outpostId,
|
|
4584
4586
|
outpost_command_id: dispatched.commandId,
|
|
@@ -4586,7 +4588,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4586
4588
|
...(action === "message"
|
|
4587
4589
|
? { delivered_to: { host: outpostId, lane_id: lane.lane_id, mechanism: "outposts_exec" } }
|
|
4588
4590
|
: {}),
|
|
4589
|
-
}
|
|
4591
|
+
};
|
|
4592
|
+
const silentMessage = action === "message" && "silent" in options && options.silent === true;
|
|
4593
|
+
if (!silentMessage)
|
|
4594
|
+
printJson(response);
|
|
4595
|
+
return action === "message" ? response : undefined;
|
|
4590
4596
|
}
|
|
4591
4597
|
function dispatchedOwnerLane(ctx, laneId, outpostId, rawLane, behavior) {
|
|
4592
4598
|
if (behavior.syncPrimary !== false) {
|
|
@@ -7426,15 +7432,16 @@ async function dispatchPrimaryLaneMessage(ctx, laneId, options) {
|
|
|
7426
7432
|
remote.accepted !== true) {
|
|
7427
7433
|
throw new LanesError("outpost_delivery_unconfirmed", `Primary completed exec without a confirmed message result for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: confirmedCommandId });
|
|
7428
7434
|
}
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7435
|
+
const response = {
|
|
7436
|
+
...remote,
|
|
7437
|
+
outpost_id: outpostId,
|
|
7438
|
+
outpost_command_id: confirmedCommandId,
|
|
7439
|
+
outpost_transport: "outposts_exec",
|
|
7440
|
+
delivered_to: { host: outpostId, lane_id: laneId, mechanism: "outposts_exec" },
|
|
7441
|
+
};
|
|
7442
|
+
if (!options.silent)
|
|
7443
|
+
printJson(response);
|
|
7444
|
+
return response;
|
|
7438
7445
|
}
|
|
7439
7446
|
async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
|
|
7440
7447
|
if (lane.host.kind !== "outpost") {
|
|
@@ -8416,7 +8423,9 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8416
8423
|
authorizeFailedResume,
|
|
8417
8424
|
authorizeResumeProfileRepin,
|
|
8418
8425
|
casUpdateLane,
|
|
8419
|
-
dispatchOutpostLaneCommand,
|
|
8426
|
+
dispatchOutpostLaneCommand: async (commandCtx, lane, action, options, behavior) => {
|
|
8427
|
+
await dispatchOutpostLaneCommand(commandCtx, lane, action, options, behavior);
|
|
8428
|
+
},
|
|
8420
8429
|
laneIsHostedHere,
|
|
8421
8430
|
laneWithRecoveredProviderSession,
|
|
8422
8431
|
mintMessageId,
|
|
@@ -33,7 +33,10 @@ export function renderIndexTarget(target, context, args = []) {
|
|
|
33
33
|
}
|
|
34
34
|
// The shell sorts and deduplicates the `wrote` paths before JSON output.
|
|
35
35
|
const stdoutPaths = written.filter((file) => /^(workspace|companies|knowledge|workers|projects)/.test(file));
|
|
36
|
-
|
|
36
|
+
const json = JSON.stringify([...new Set(stdoutPaths)].sort(), null, 2);
|
|
37
|
+
// Windows jq emits CRLF when its JSON output is captured by the shell.
|
|
38
|
+
const lineEndings = process.platform === "win32" ? json.replace(/\n/g, "\r\n") : json;
|
|
39
|
+
(context.output ?? ((message) => process.stdout.write(message)))(`${lineEndings}\n`);
|
|
37
40
|
context.log?.(errors ? `all-indexes: errors (${errors})` : "all-indexes: ok");
|
|
38
41
|
return { written };
|
|
39
42
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
-
import { basename, countImmediate, date, exists, heading, immediateEntries, isHidden, log, mtime, sanitize, truncate, write } from "./shared.js";
|
|
2
|
+
import { basename, countImmediate, date, exists, heading, immediateEntries, isHidden, log, mtime, rel, sanitize, truncate, write } from "./shared.js";
|
|
3
3
|
function directoryDescription(context, name) {
|
|
4
4
|
const base = `workspace/social-drafts/${name}`;
|
|
5
5
|
const files = countImmediate(context.root, base, (child, file) => !isHidden(child) && fs.statSync(file).isFile());
|
|
@@ -29,7 +29,7 @@ export function renderSocialDrafts(context) {
|
|
|
29
29
|
const recent = ["x", "linkedin", "blog", "tiktok"].flatMap((channel) => immediateEntries(context.root, `${base}/${channel}`, "file").filter((file) => file.endsWith(".md"))).sort((a, b) => mtime(b) - mtime(a)).slice(0, 20);
|
|
30
30
|
const lines = ["# Social Drafts", "", `> Auto-generated. Updated: ${date(context.now)}`, "", "## Recent Drafts", ""];
|
|
31
31
|
for (const file of recent) {
|
|
32
|
-
const relative = file.slice(
|
|
32
|
+
const relative = rel(context.root, file).slice("workspace/social-drafts/".length);
|
|
33
33
|
lines.push(`- \`${relative}\` - ${truncate(sanitize(heading(file) || basename(file).slice(0, -3)), 100)}`);
|
|
34
34
|
}
|
|
35
35
|
lines.push("", "## Directories", "", "| Name | Description |", "|------|-------------|");
|
|
@@ -355,8 +355,6 @@ function blockedKeepAliveOutcome(lane, stop, nextEnvelopeMs) {
|
|
|
355
355
|
: Math.abs(terminal - stop.mtimeMs) <= 10 * 60_000;
|
|
356
356
|
if (!sameStop)
|
|
357
357
|
return [];
|
|
358
|
-
if (entry.event === "keep_alive_blocked_skipped" && entry.reason !== "flag_off")
|
|
359
|
-
return [];
|
|
360
358
|
return [{
|
|
361
359
|
status: entry.event === "keep_alive_blocked_resumed" ? "woken" : "skipped",
|
|
362
360
|
...(entry.reason === undefined ? {} : { reason: entry.reason }),
|
|
@@ -2328,8 +2328,9 @@ async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, no
|
|
|
2328
2328
|
recordKeepAliveSkip(hqRoot, lane, "missing_interruption_time", summary, deps, "blocked terminal time is invalid");
|
|
2329
2329
|
return;
|
|
2330
2330
|
}
|
|
2331
|
-
if (activeLaneStories(lane).length === 0) {
|
|
2331
|
+
if (laneStories(lane).length > 0 && activeLaneStories(lane).length === 0) {
|
|
2332
2332
|
summary.skipped.push({ kind: "lane", id: lane.lane_id, why: "keep_alive_no_open_stories" });
|
|
2333
|
+
recordBlockedKeepAliveSkipEvent(hqRoot, lane, "no_open_stories", summary, deps);
|
|
2333
2334
|
return;
|
|
2334
2335
|
}
|
|
2335
2336
|
let pendingQuestion = questions.some((question) => question.lane_id === lane.lane_id && question.status === "pending");
|
|
@@ -2496,6 +2497,27 @@ function recordBlockedKeepAliveFlagOff(hqRoot, lane, summary, deps) {
|
|
|
2496
2497
|
markDirty(summary, { kind: "lane", id: lane.lane_id, why: `blocked keep-alive skip record failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
2497
2498
|
}
|
|
2498
2499
|
}
|
|
2500
|
+
function recordBlockedKeepAliveSkipEvent(hqRoot, lane, reason, summary, deps) {
|
|
2501
|
+
const terminalAt = lane.last_envelope?.at;
|
|
2502
|
+
if (deps.dryRun || lane.last_envelope?.decision !== "blocked" || terminalAt === undefined)
|
|
2503
|
+
return;
|
|
2504
|
+
if (lane.status_gate_history?.some((entry) => entry.event === "keep_alive_blocked_skipped" && entry.terminal_at === terminalAt && entry.reason === reason))
|
|
2505
|
+
return;
|
|
2506
|
+
try {
|
|
2507
|
+
casLaneIf(hqRoot, lane.lane_id, (current) => current.version === lane.version && current.state === "blocked" && current.last_envelope?.at === terminalAt, (current) => ({
|
|
2508
|
+
...current,
|
|
2509
|
+
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
2510
|
+
event: "keep_alive_blocked_skipped",
|
|
2511
|
+
at: deps.now().toISOString(),
|
|
2512
|
+
terminal_at: terminalAt,
|
|
2513
|
+
reason,
|
|
2514
|
+
}),
|
|
2515
|
+
}), false);
|
|
2516
|
+
}
|
|
2517
|
+
catch (err) {
|
|
2518
|
+
markDirty(summary, { kind: "lane", id: lane.lane_id, why: `blocked keep-alive skip record failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2499
2521
|
function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, deps) {
|
|
2500
2522
|
if (deps.dryRun || lane.status_gate_history?.some((entry) => entry.event === "keep_alive_blocked_grace" && entry.terminal_at === terminalAt && entry.resume_skipped_reason === reason))
|
|
2501
2523
|
return;
|
|
@@ -449,7 +449,7 @@ export interface KeepAliveBlockedHistoryEntry {
|
|
|
449
449
|
at: string;
|
|
450
450
|
terminal_at: string;
|
|
451
451
|
resume_skipped_reason?: "open_question" | "blocked_grace";
|
|
452
|
-
reason?: "flag_off" | "backoff" | "next_check_at_due";
|
|
452
|
+
reason?: "flag_off" | "backoff" | "next_check_at_due" | "no_open_stories";
|
|
453
453
|
resume_queued?: boolean;
|
|
454
454
|
}
|
|
455
455
|
export interface KeepAliveResumeHistoryEntry {
|