@indigoai-us/hq-cli 5.315.0 → 5.317.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 +13 -0
- package/assets/scaffold/core/scripts/detect-stale-review-base.sh +199 -0
- package/assets/scaffold/core/scripts/repo-run-registry.sh +408 -0
- package/assets/scaffold/core/scripts/session-journal.sh +301 -0
- package/assets/scaffold/core/scripts/session-project.sh +800 -0
- package/assets/scaffold/core/scripts/session-title-config.sh +76 -0
- package/assets/scaffold/core/scripts/session-title.sh +305 -0
- package/assets/scaffold/core/scripts/share-suggestion-state.sh +541 -0
- package/dist/command-catalog.generated.d.ts +97 -0
- package/dist/command-catalog.generated.js +120 -0
- package/dist/commands/bot-jobs.d.ts +5 -1
- package/dist/commands/bot-jobs.js +24 -4
- package/dist/commands/core-detect-stale-review-base-native.d.ts +23 -0
- package/dist/commands/core-detect-stale-review-base-native.js +178 -0
- package/dist/commands/core-hook-state-helpers-native.d.ts +11 -0
- package/dist/commands/core-hook-state-helpers-native.js +74 -0
- package/dist/commands/core-repo-run-registry-native.d.ts +19 -0
- package/dist/commands/core-repo-run-registry-native.js +357 -0
- package/dist/commands/core-session-helper-runtime.d.ts +10 -0
- package/dist/commands/core-session-helper-runtime.js +7 -0
- package/dist/commands/core-session-journal-native.d.ts +4 -0
- package/dist/commands/core-session-journal-native.js +327 -0
- package/dist/commands/core-session-project-native.d.ts +3 -0
- package/dist/commands/core-session-project-native.js +855 -0
- package/dist/commands/core-session-title-config-native.d.ts +4 -0
- package/dist/commands/core-session-title-config-native.js +77 -0
- package/dist/commands/core-session-title-native.d.ts +3 -0
- package/dist/commands/core-session-title-native.js +306 -0
- package/dist/commands/core-share-suggestion-state-native.d.ts +18 -0
- package/dist/commands/core-share-suggestion-state-native.js +445 -0
- package/dist/commands/core.js +66 -0
- package/dist/commands/lanes.js +35 -5
- package/dist/fast-core.d.ts +1 -0
- package/dist/fast-core.js +10 -6
- package/dist/fast-scaffold.js +33 -0
- package/dist/lib/bot/job-quiet-flag.d.ts +4 -0
- package/dist/lib/bot/job-quiet-flag.js +14 -0
- package/dist/lib/bot/job-run.d.ts +5 -0
- package/dist/lib/bot/job-run.js +23 -3
- package/dist/lib/bot/run.d.ts +2 -0
- package/dist/lib/bot/run.js +15 -3
- package/dist/lib/core-utils/common.d.ts +0 -1
- package/dist/lib/core-utils/common.js +0 -9
- package/dist/lib/lanes/autopilot.d.ts +5 -1
- package/dist/lib/lanes/autopilot.js +72 -3
- package/dist/lib/lanes/disk-guard.d.ts +3 -4
- package/dist/lib/lanes/disk-guard.js +2 -2
- package/dist/lib/lanes/flag-keys.d.ts +3 -0
- package/dist/lib/lanes/flag-keys.js +3 -0
- package/dist/lib/lanes/hooks-install.js +78 -2
- package/dist/lib/lanes/keep-alive-audit.d.ts +6 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +9 -0
- package/dist/lib/lanes/outpost-dispatch.js +22 -0
- package/dist/utils/version-gate.js +8 -7
- package/package.json +1 -1
package/dist/commands/lanes.js
CHANGED
|
@@ -45,7 +45,7 @@ import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } fr
|
|
|
45
45
|
import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
|
|
46
46
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
47
47
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
48
|
-
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
48
|
+
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, buildOutpostLegacyLaneUlidListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
49
49
|
import { outpostHireSummaryCacheKey, OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, readOutpostHireSummaryCache, readOutpostHireSummaryCacheIncludingStale, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
|
|
50
50
|
import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
|
|
51
51
|
import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
|
|
@@ -781,6 +781,7 @@ function validateOwnerLaneHireSummary(input) {
|
|
|
781
781
|
function laneListQueryFlags(query) {
|
|
782
782
|
return [
|
|
783
783
|
...(query.laneIds?.length ? ["--lane"] : []),
|
|
784
|
+
...(query.laneUlids?.length ? ["--lane-ulid"] : []),
|
|
784
785
|
...(query.active ? ["--active"] : []),
|
|
785
786
|
...(query.hierarchy?.length ? ["--hierarchy"] : []),
|
|
786
787
|
...(query.snapshot ? ["--snapshot"] : []),
|
|
@@ -804,6 +805,14 @@ async function dispatchOutpostLaneList(ctx, outpostId, query, timeoutSeconds, wa
|
|
|
804
805
|
});
|
|
805
806
|
if (query.strictSnapshot || !rejectedLaneListFlag(filtered, query))
|
|
806
807
|
return filtered;
|
|
808
|
+
if (rejectedLaneListFlag(filtered, query) === "--lane-ulid" && query.laneUlids?.length === 1) {
|
|
809
|
+
return ctx.dispatchOutpostExec({
|
|
810
|
+
outpostId,
|
|
811
|
+
command: buildOutpostLegacyLaneUlidListCommand({ hqBinary, ulid: query.laneUlids[0] }),
|
|
812
|
+
timeoutSeconds,
|
|
813
|
+
waitMs,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
807
816
|
return ctx.dispatchOutpostExec({
|
|
808
817
|
outpostId,
|
|
809
818
|
command: buildOutpostLaneListCommand({ hqBinary }),
|
|
@@ -1206,15 +1215,20 @@ async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(h
|
|
|
1206
1215
|
};
|
|
1207
1216
|
}
|
|
1208
1217
|
const outpostLaneListMemo = new WeakMap();
|
|
1218
|
+
const MAX_OUTPOST_LANE_LIST_BYTES = 480 * 1024;
|
|
1209
1219
|
function normalizedLaneListQuery(query) {
|
|
1210
1220
|
const laneIds = query.laneIds === undefined
|
|
1211
1221
|
? undefined
|
|
1212
1222
|
: [...new Set(query.laneIds)].sort((a, b) => a.localeCompare(b));
|
|
1223
|
+
const laneUlids = query.laneUlids === undefined
|
|
1224
|
+
? undefined
|
|
1225
|
+
: [...new Set(query.laneUlids.map((ulid) => ulid.toUpperCase()))].sort((a, b) => a.localeCompare(b));
|
|
1213
1226
|
const hierarchy = query.hierarchy === undefined
|
|
1214
1227
|
? undefined
|
|
1215
1228
|
: [...new Set(query.hierarchy)].sort((a, b) => a.localeCompare(b));
|
|
1216
1229
|
return {
|
|
1217
1230
|
...(laneIds === undefined ? {} : { laneIds }),
|
|
1231
|
+
...(laneUlids === undefined ? {} : { laneUlids }),
|
|
1218
1232
|
...(query.active ? { active: true } : {}),
|
|
1219
1233
|
...(hierarchy === undefined ? {} : { hierarchy }),
|
|
1220
1234
|
...(query.snapshot ? { snapshot: true } : {}),
|
|
@@ -1223,7 +1237,7 @@ function normalizedLaneListQuery(query) {
|
|
|
1223
1237
|
};
|
|
1224
1238
|
}
|
|
1225
1239
|
async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions = {}) {
|
|
1226
|
-
const targetedLookup = query.hierarchy !== undefined || query.laneIds !== undefined;
|
|
1240
|
+
const targetedLookup = query.hierarchy !== undefined || query.laneIds !== undefined || query.laneUlids !== undefined;
|
|
1227
1241
|
const waitMs = targetedLookup ? outpostSingleLaneWaitMs(ctx.env) : CAPACITY_PROBE_TIMEOUT_MS;
|
|
1228
1242
|
const dispatchWaitMs = dispatchOptions.waitMs ?? waitMs;
|
|
1229
1243
|
const dispatchTimeoutSeconds = dispatchOptions.timeoutSeconds ?? Math.ceil(dispatchWaitMs / 1000);
|
|
@@ -1249,6 +1263,10 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1249
1263
|
exit_code: result.exitCode,
|
|
1250
1264
|
});
|
|
1251
1265
|
}
|
|
1266
|
+
const stdoutBytes = Buffer.byteLength(result.stdout, "utf8");
|
|
1267
|
+
if (stdoutBytes >= MAX_OUTPOST_LANE_LIST_BYTES) {
|
|
1268
|
+
throw new LanesError("outpost_lane_list_too_large", `Outpost ${outpostId} lane-list response reached the ${Math.floor(MAX_OUTPOST_LANE_LIST_BYTES / 1024)} KiB Outpost exec output limit`, { outpost_id: outpostId, command_id: result.commandId, stdout_bytes: stdoutBytes });
|
|
1269
|
+
}
|
|
1252
1270
|
let body;
|
|
1253
1271
|
try {
|
|
1254
1272
|
body = JSON.parse(result.stdout);
|
|
@@ -1625,7 +1643,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1625
1643
|
let ownerLanes;
|
|
1626
1644
|
try {
|
|
1627
1645
|
ownerLanes = await readOutpostLaneRecords(ctx, outpostId, hostlessUlidLookup
|
|
1628
|
-
? { snapshot: true }
|
|
1646
|
+
? { laneUlids: [requestedUlid], snapshot: true }
|
|
1629
1647
|
: { hierarchy: [laneId], snapshot: true });
|
|
1630
1648
|
probedOutpostIds.push(outpostId);
|
|
1631
1649
|
}
|
|
@@ -4849,6 +4867,12 @@ async function runList(ctx, opts) {
|
|
|
4849
4867
|
new Set(requestedLaneIds).size !== requestedLaneIds.length) {
|
|
4850
4868
|
throw new LanesError("lane_list_filter_invalid", "--lane must be repeated with distinct, non-empty lane ids", { lane_ids: requestedLaneIds });
|
|
4851
4869
|
}
|
|
4870
|
+
const requestedLaneUlids = opts.laneUlid ?? [];
|
|
4871
|
+
if (!Array.isArray(requestedLaneUlids) ||
|
|
4872
|
+
requestedLaneUlids.some((ulid) => typeof ulid !== "string" || laneUlidPart(ulid) !== ulid.toUpperCase()) ||
|
|
4873
|
+
new Set(requestedLaneUlids.map((ulid) => ulid.toUpperCase())).size !== requestedLaneUlids.length) {
|
|
4874
|
+
throw new LanesError("lane_list_ulid_filter_invalid", "--lane-ulid must be repeated with distinct 26-character ULIDs", { lane_ulids: requestedLaneUlids });
|
|
4875
|
+
}
|
|
4852
4876
|
if (opts.hierarchy !== undefined &&
|
|
4853
4877
|
(!Array.isArray(opts.hierarchy) || opts.hierarchy.length === 0 ||
|
|
4854
4878
|
opts.hierarchy.some((laneId) => typeof laneId !== "string" || !laneId.trim() || laneId.trim() !== laneId) ||
|
|
@@ -4869,11 +4893,13 @@ async function runList(ctx, opts) {
|
|
|
4869
4893
|
company = await ctx.resolveCompany(opts.company);
|
|
4870
4894
|
}
|
|
4871
4895
|
const requestedIds = new Set(requestedLaneIds);
|
|
4872
|
-
const
|
|
4896
|
+
const requestedUlidSet = new Set(requestedLaneUlids.map((ulid) => ulid.toUpperCase()));
|
|
4897
|
+
const canReadOnlyRequestedIds = opts.snapshot && (requestedLaneIds.length > 0 || requestedLaneUlids.length > 0) &&
|
|
4873
4898
|
!opts.active && opts.hierarchy === undefined;
|
|
4874
4899
|
const laneSource = canReadOnlyRequestedIds
|
|
4875
4900
|
? listLaneIds(hqRoot)
|
|
4876
|
-
.filter((laneId) => requestedIds.has(laneId)
|
|
4901
|
+
.filter((laneId) => requestedIds.has(laneId) ||
|
|
4902
|
+
(laneUlidPart(laneId) !== undefined && requestedUlidSet.has(laneUlidPart(laneId))))
|
|
4877
4903
|
.map((laneId) => readLane(hqRoot, laneId))
|
|
4878
4904
|
: loadAllLanes(hqRoot);
|
|
4879
4905
|
const hierarchyIds = opts.hierarchy === undefined
|
|
@@ -4882,6 +4908,8 @@ async function runList(ctx, opts) {
|
|
|
4882
4908
|
const matchesLaneFilters = (lane, includeState) => {
|
|
4883
4909
|
if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
|
|
4884
4910
|
return false;
|
|
4911
|
+
if (requestedUlidSet.size > 0 && !requestedUlidSet.has(laneUlidPart(lane.lane_id) ?? ""))
|
|
4912
|
+
return false;
|
|
4885
4913
|
if (includeState && opts.active && isTerminalLaneState(lane.state))
|
|
4886
4914
|
return false;
|
|
4887
4915
|
if (hierarchyIds && !hierarchyIds.has(lane.lane_id))
|
|
@@ -8424,6 +8452,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8424
8452
|
.option("--state <state>", `Filter by state (${LANE_STATES.join("|")})`)
|
|
8425
8453
|
.option("--senior <ref>", "Filter by senior ref")
|
|
8426
8454
|
.option("--lane <id>", "Filter by lane id (repeatable)", (value, previous = []) => [...previous, value], [])
|
|
8455
|
+
.addOption(new Option("--lane-ulid <ulid>", "Filter by lane ULID for internal owner lookup").hideHelp()
|
|
8456
|
+
.argParser((value, previous = []) => [...previous, value]))
|
|
8427
8457
|
.option("--active", "Include only lanes that are not terminal")
|
|
8428
8458
|
.addOption(new Option("--hierarchy <lane-id>").hideHelp()
|
|
8429
8459
|
.argParser((value, previous = []) => [...previous, value]))
|
package/dist/fast-core.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* entries use the bundled-script runner; native entries import their one handler
|
|
7
7
|
* module. The registration in `core.ts` remains the complete fallback path.
|
|
8
8
|
*/
|
|
9
|
+
export declare function readStdinIfPiped(stream: NodeJS.ReadStream): Promise<string>;
|
|
9
10
|
/**
|
|
10
11
|
* Run the fast-path core command named in `argv` and exit with its code.
|
|
11
12
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
package/dist/fast-core.js
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
import { resolveLiveRoot } from "./utils/hq-roots.js";
|
|
10
10
|
import { runBundledScript } from "./utils/run-bundled-script.js";
|
|
11
11
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
12
|
+
export async function readStdinIfPiped(stream) {
|
|
13
|
+
if (stream.isTTY)
|
|
14
|
+
return "";
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for await (const chunk of stream)
|
|
17
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
18
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
19
|
+
}
|
|
12
20
|
/**
|
|
13
21
|
* Run the fast-path core command named in `argv` and exit with its code.
|
|
14
22
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
|
@@ -60,12 +68,8 @@ export async function runFastCore(argv) {
|
|
|
60
68
|
throw new Error(`Native fast handler ${entry.exportName} was not exported`);
|
|
61
69
|
}
|
|
62
70
|
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
for await (const chunk of process.stdin)
|
|
66
|
-
chunks.push(chunk);
|
|
67
|
-
}
|
|
68
|
-
const code = await handler(parsed.args, readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
71
|
+
const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
|
|
72
|
+
const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
69
73
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
70
74
|
// stderr message must survive the native route's process boundary.
|
|
71
75
|
process.exitCode = code;
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -172,6 +172,23 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
172
172
|
shellCwd: "caller",
|
|
173
173
|
shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
|
|
174
174
|
})),
|
|
175
|
+
...["share-suggestion-state", "repo-run-registry", "detect-stale-review-base"].map((name) => ({
|
|
176
|
+
name,
|
|
177
|
+
kind: "native",
|
|
178
|
+
modulePath: "./commands/core-hook-state-helpers-native.js",
|
|
179
|
+
exportName: name === "share-suggestion-state"
|
|
180
|
+
? "runShareSuggestionStateFast"
|
|
181
|
+
: name === "repo-run-registry"
|
|
182
|
+
? "runRepoRunRegistryFast"
|
|
183
|
+
: "runDetectStaleReviewBaseFast",
|
|
184
|
+
flagKey: "hq-core-native-fast-route",
|
|
185
|
+
familyFlagKey: "core-native-hook-helpers",
|
|
186
|
+
asset: `core/scripts/${name}.sh`,
|
|
187
|
+
root: "live",
|
|
188
|
+
shellCwd: "live",
|
|
189
|
+
shellHqRoot: "live",
|
|
190
|
+
...(name === "share-suggestion-state" ? { readsStdin: true } : {}),
|
|
191
|
+
})),
|
|
175
192
|
{
|
|
176
193
|
name: "resolve-company",
|
|
177
194
|
kind: "native",
|
|
@@ -184,6 +201,22 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
184
201
|
shellCwd: "live",
|
|
185
202
|
shellHqRoot: "live",
|
|
186
203
|
},
|
|
204
|
+
...["session-journal", "session-project", "session-title", "session-title-config"].map((name) => ({
|
|
205
|
+
name,
|
|
206
|
+
kind: "native",
|
|
207
|
+
modulePath: `./commands/core-${name}-native.js`,
|
|
208
|
+
exportName: name === "session-journal" ? "runSessionJournalFast"
|
|
209
|
+
: name === "session-project" ? "runSessionProjectFast"
|
|
210
|
+
: name === "session-title" ? "runSessionTitleFast"
|
|
211
|
+
: "runSessionTitleConfigFast",
|
|
212
|
+
flagKey: "hq-core-native-fast-route",
|
|
213
|
+
familyFlagKey: "core-native-hook-helpers",
|
|
214
|
+
asset: `core/scripts/${name}.sh`,
|
|
215
|
+
root: "live",
|
|
216
|
+
shellCwd: "live",
|
|
217
|
+
shellHqRoot: "live",
|
|
218
|
+
...(name === "session-project" ? { readsStdin: true, readsStdinFor: (args) => args[0] === "ingest-plan" } : {}),
|
|
219
|
+
})),
|
|
187
220
|
...[
|
|
188
221
|
{ name: "eval-trigger", modulePath: "./commands/core-eval-trigger-native.js", exportName: "runEvalTriggerFast", asset: "core/scripts/eval-trigger.sh", root: "cwd", shellCwd: "caller", shellHqRoot: "none", readsStdinFor: (args) => args[0] === "--check" },
|
|
189
222
|
{ name: "migrate-policy-triggers", modulePath: "./commands/core-migrate-policy-triggers-native.js", exportName: "runMigratePolicyTriggersFast", asset: "core/scripts/migrate-policy-triggers.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Default-off rollout gate for per-job quiet success delivery. */
|
|
2
|
+
export const BOT_JOB_QUIET_OPTION_FLAG = "cli.bot-job-quiet-option";
|
|
3
|
+
export async function botJobQuietOptionEnabled() {
|
|
4
|
+
try {
|
|
5
|
+
const { resolveProcessFlagGate } = await import("../flag-registry.js");
|
|
6
|
+
return resolveProcessFlagGate(BOT_JOB_QUIET_OPTION_FLAG, { fallback: false }, () => false);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
const errorClass = error instanceof Error ? error.name : "unknown";
|
|
10
|
+
console.warn(`hq: Could not resolve ${BOT_JOB_QUIET_OPTION_FLAG} (${errorClass}); quiet jobs stay disabled.`);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=job-quiet-flag.js.map
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* Everything in this file is pure so the rules are unit-testable.
|
|
21
21
|
*/
|
|
22
22
|
import type { InboxItem } from "./api.js";
|
|
23
|
+
/** Private metadata persisted in the job's existing opaque context object. */
|
|
24
|
+
export declare const JOB_RUN_OPTIONS_CONTEXT_KEY = "__hq_cli_job_options_v1";
|
|
25
|
+
export declare function isQuietJobContext(context: unknown): boolean;
|
|
23
26
|
/** The job fields an inbox item carries when `channel === "job"`. */
|
|
24
27
|
export interface JobRunFields {
|
|
25
28
|
jobId: string;
|
|
@@ -32,6 +35,8 @@ export interface JobRunFields {
|
|
|
32
35
|
/** Delivery deadline (ISO-8601); past it, this is a catch-up. */
|
|
33
36
|
staleAfter?: string;
|
|
34
37
|
requestId?: string;
|
|
38
|
+
/** Whether successful runs should be silent when the local rollout flag is on. */
|
|
39
|
+
quiet?: boolean;
|
|
35
40
|
}
|
|
36
41
|
export declare function isJobItem(item: Pick<InboxItem, "channel">): boolean;
|
|
37
42
|
/**
|
package/dist/lib/bot/job-run.js
CHANGED
|
@@ -19,6 +19,25 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Everything in this file is pure so the rules are unit-testable.
|
|
21
21
|
*/
|
|
22
|
+
/** Private metadata persisted in the job's existing opaque context object. */
|
|
23
|
+
export const JOB_RUN_OPTIONS_CONTEXT_KEY = "__hq_cli_job_options_v1";
|
|
24
|
+
function objectRecord(value) {
|
|
25
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
export function isQuietJobContext(context) {
|
|
30
|
+
const options = objectRecord(objectRecord(context)?.[JOB_RUN_OPTIONS_CONTEXT_KEY]);
|
|
31
|
+
return options?.quiet === true;
|
|
32
|
+
}
|
|
33
|
+
function visibleJobContext(context) {
|
|
34
|
+
if (!(JOB_RUN_OPTIONS_CONTEXT_KEY in context)) {
|
|
35
|
+
return Object.keys(context).length ? context : null;
|
|
36
|
+
}
|
|
37
|
+
const visible = { ...context };
|
|
38
|
+
delete visible[JOB_RUN_OPTIONS_CONTEXT_KEY];
|
|
39
|
+
return Object.keys(visible).length ? visible : null;
|
|
40
|
+
}
|
|
22
41
|
export function isJobItem(item) {
|
|
23
42
|
return item.channel === "job";
|
|
24
43
|
}
|
|
@@ -37,6 +56,7 @@ export function readJobRun(item) {
|
|
|
37
56
|
return {
|
|
38
57
|
jobId,
|
|
39
58
|
prompt,
|
|
59
|
+
...(isQuietJobContext(item.context) ? { quiet: true } : {}),
|
|
40
60
|
...(item.context !== undefined && item.context !== null ? { context: item.context } : {}),
|
|
41
61
|
...(typeof item.script === "string" && item.script.trim() ? { script: item.script } : {}),
|
|
42
62
|
...(typeof item.firedAt === "string" && item.firedAt.trim() ? { firedAt: item.firedAt.trim() } : {}),
|
|
@@ -51,10 +71,10 @@ export function jobContextText(context) {
|
|
|
51
71
|
if (typeof context === "string")
|
|
52
72
|
return context.trim() || null;
|
|
53
73
|
try {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
74
|
+
const visible = visibleJobContext(context);
|
|
75
|
+
if (!visible)
|
|
56
76
|
return null;
|
|
57
|
-
return JSON.stringify(
|
|
77
|
+
return JSON.stringify(visible, null, 2);
|
|
58
78
|
}
|
|
59
79
|
catch {
|
|
60
80
|
return null;
|
package/dist/lib/bot/run.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ export interface BotRunDeps {
|
|
|
120
120
|
inboxIntervalMs?: number;
|
|
121
121
|
/** Cached, default-off reader for the local inbox polling gate. */
|
|
122
122
|
inboxPollBackoffEnabled?: () => Promise<boolean>;
|
|
123
|
+
/** Default-off gate for the per-job quiet-success option. */
|
|
124
|
+
jobQuietOptionEnabled?: () => boolean | Promise<boolean>;
|
|
123
125
|
heartbeatIntervalMs?: number;
|
|
124
126
|
/** Override the rendered system prompt (tests). */
|
|
125
127
|
systemPromptOverride?: string;
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -69,6 +69,7 @@ import { attachmentTurnBlock, downloadTurnAttachments } from "./attachments.js";
|
|
|
69
69
|
import { inboxEntryAttachments } from "../agent-kit/inbox-state.js";
|
|
70
70
|
import { isAgentSender, isDmItem, isMetaNonReply, resolveReplyTarget } from "./room-policy.js";
|
|
71
71
|
import { buildJobTurnPrompt, isCatchUpRun, isJobItem, jobReportHeader, jobRunOutput, machineTimeZone, planJobRuns, stripJobHeader, SUPERSEDED_DISPOSITION } from "./job-run.js";
|
|
72
|
+
import { botJobQuietOptionEnabled } from "./job-quiet-flag.js";
|
|
72
73
|
import { clearPendingJobCompletion, isCompletionDue, isCompletionExpired, isCompletionForOtherAgent, readPendingJobCompletions, recordPendingJobCompletion } from "./job-completions.js";
|
|
73
74
|
import { isRuntimeSignInFailure, SIGN_IN_RETRY_MS, signInNeededText } from "./runtime-sign-in.js";
|
|
74
75
|
import { HQ_BOT_AGENT_UID_ENV } from "../../commands/onboard-bot-membership.js";
|
|
@@ -277,6 +278,7 @@ export async function runBot(deps) {
|
|
|
277
278
|
const runTurn = deps.runTurn ?? ((rt, input) => runRuntimeTurn(rt, input));
|
|
278
279
|
const inboxIntervalMs = deps.inboxIntervalMs ?? INBOX_POLL_INTERVAL_MS;
|
|
279
280
|
const inboxPollBackoffEnabled = deps.inboxPollBackoffEnabled ?? createInboxPollBackoffFlagReader(config);
|
|
281
|
+
const jobQuietOptionEnabled = deps.jobQuietOptionEnabled ?? botJobQuietOptionEnabled;
|
|
280
282
|
const heartbeatIntervalMs = deps.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;
|
|
281
283
|
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
282
284
|
const fallback = () => defaultBotStatus(pid, config.runtime, now);
|
|
@@ -763,7 +765,7 @@ export async function runBot(deps) {
|
|
|
763
765
|
...(isSetupBot ? { dropPreToolMessages: true } : {}),
|
|
764
766
|
...deps.progress,
|
|
765
767
|
...(args.workingNotice === false || silenceable || isSetupBot ? { workingNoticeMs: 0 } : {}),
|
|
766
|
-
...(silenceable ? { buffer: true } : {}),
|
|
768
|
+
...(silenceable || args.suppressOutput ? { buffer: true } : {}),
|
|
767
769
|
send: makeSendFn(args.send),
|
|
768
770
|
...(args.sendFinal ? { sendFinal: makeSendFn(args.sendFinal) } : {}),
|
|
769
771
|
});
|
|
@@ -788,6 +790,11 @@ export async function runBot(deps) {
|
|
|
788
790
|
// meta chatter that let two bots loop on 2026-09-25.
|
|
789
791
|
const stayQuiet = silenceable && isMetaNonReply(turn.text);
|
|
790
792
|
const sentinelOrEmpty = containsNoReplySentinel(turn.text) || !turn.text.trim() || stayQuiet;
|
|
793
|
+
if (args.suppressOutput) {
|
|
794
|
+
await poster.abandon();
|
|
795
|
+
const finalText = sentinelOrEmpty ? "" : turn.text;
|
|
796
|
+
return { outcome: { ok: true, reply: finalText.trim(), ...(sentinelOrEmpty ? { silent: true } : {}) }, poster };
|
|
797
|
+
}
|
|
791
798
|
// Unmentioned room turn: a no_reply sentinel (bare, fenced, or with
|
|
792
799
|
// preamble), an empty/whitespace reply, or a stay-quiet phrase means stay
|
|
793
800
|
// quiet. Abandon the poster (it posts nothing) and return a silent outcome;
|
|
@@ -1182,6 +1189,7 @@ export async function runBot(deps) {
|
|
|
1182
1189
|
return;
|
|
1183
1190
|
}
|
|
1184
1191
|
const catchUp = isCatchUpRun(job, now());
|
|
1192
|
+
const quietJob = target.job.quiet === true && await jobQuietOptionEnabled();
|
|
1185
1193
|
const attempt = (attempts.get(id) ?? 0) + 1;
|
|
1186
1194
|
attempts.set(id, attempt);
|
|
1187
1195
|
const tReceived = now().getTime();
|
|
@@ -1268,9 +1276,10 @@ export async function runBot(deps) {
|
|
|
1268
1276
|
sendFinal,
|
|
1269
1277
|
// A scheduled run has nobody waiting in a chat: no "working on it" filler.
|
|
1270
1278
|
workingNotice: false,
|
|
1279
|
+
...(quietJob ? { suppressOutput: true } : {}),
|
|
1271
1280
|
});
|
|
1272
1281
|
if (outcome.ok) {
|
|
1273
|
-
if (!reportDelivered(outcome.reply)) {
|
|
1282
|
+
if (!quietJob && !reportDelivered(outcome.reply)) {
|
|
1274
1283
|
// The work finished but the REPORT did not fully reach the DM — even if
|
|
1275
1284
|
// a progress post did. Keep it (and the run un-reported and un-acked)
|
|
1276
1285
|
// so the next poll delivers it, resuming at the parts already read,
|
|
@@ -1285,7 +1294,10 @@ export async function runBot(deps) {
|
|
|
1285
1294
|
await api.ackInbox(config.agentUid, id);
|
|
1286
1295
|
clearInflight(dir, id);
|
|
1287
1296
|
attempts.delete(id);
|
|
1288
|
-
setStatus({
|
|
1297
|
+
setStatus({
|
|
1298
|
+
...(!quietJob ? { repliesSent: (readStatus()?.repliesSent ?? 0) + 1 } : {}),
|
|
1299
|
+
lastActivityAt: now().toISOString(),
|
|
1300
|
+
});
|
|
1289
1301
|
log("info", `job ${jobId} run ${id} done (${outcome.reply.length} chars${catchUp ? ", catch-up" : ""})`);
|
|
1290
1302
|
return;
|
|
1291
1303
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
2
|
export function ioFor(io = {}) {
|
|
4
3
|
return {
|
|
5
4
|
stdout: io.stdout ?? ((text) => process.stdout.write(text)),
|
|
@@ -39,12 +38,4 @@ export function command(command, args, cwd) {
|
|
|
39
38
|
}
|
|
40
39
|
return { status: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
41
40
|
}
|
|
42
|
-
export function exists(pathname) {
|
|
43
|
-
try {
|
|
44
|
-
return Boolean(fs.lstatSync(pathname));
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
41
|
//# sourceMappingURL=common.js.map
|
|
@@ -13,6 +13,7 @@ export declare const LEAD_AUTOPILOT_REVIEW_DELAY_MS: number;
|
|
|
13
13
|
export declare const LEAD_AUTOPILOT_FILL_DELAY_MS: number;
|
|
14
14
|
export declare const LEAD_AUTOPILOT_PROMPT_LOOP = "personal/knowledge/team-lead-operating-loop.md";
|
|
15
15
|
export declare const LEAD_AUTOPILOT_KPI_STALE_AFTER_MINUTES = 55;
|
|
16
|
+
export declare const LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES = 55;
|
|
16
17
|
export declare const LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG = "lanes.autopilot-per-lead-errors";
|
|
17
18
|
export declare const LEAD_AUTOPILOT_PER_LEAD_ERRORS_DEFAULT_ENABLED = false;
|
|
18
19
|
export interface RegisteredLead {
|
|
@@ -22,7 +23,7 @@ export interface RegisteredLead {
|
|
|
22
23
|
status: string;
|
|
23
24
|
prd: string;
|
|
24
25
|
}
|
|
25
|
-
export type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "stale_kpi" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane" | "missed_deadline";
|
|
26
|
+
export type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "stale_kpi" | "stale_headline" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane" | "missed_deadline";
|
|
26
27
|
export interface AutopilotWake {
|
|
27
28
|
condition: AutopilotCondition;
|
|
28
29
|
lead_lane: string;
|
|
@@ -31,6 +32,8 @@ export interface AutopilotWake {
|
|
|
31
32
|
prompt: string;
|
|
32
33
|
fingerprint: string;
|
|
33
34
|
kpi_id?: string;
|
|
35
|
+
/** Headline episode represented inside the KPI batch notice, if any. */
|
|
36
|
+
headline_fingerprint?: string;
|
|
34
37
|
}
|
|
35
38
|
export interface AutopilotKpiStatus {
|
|
36
39
|
lead_lane: string;
|
|
@@ -109,6 +112,7 @@ export declare function planLeadAutopilotWakes(input: {
|
|
|
109
112
|
keepAliveResumed?: boolean;
|
|
110
113
|
kpiStalenessEnabled?: boolean;
|
|
111
114
|
kpiStalenessBatchEnabled?: boolean;
|
|
115
|
+
headlineStalenessEnabled?: boolean;
|
|
112
116
|
}): AutopilotWake[];
|
|
113
117
|
export declare function runLeadAutopilotPass(input: {
|
|
114
118
|
hqRoot: string;
|
|
@@ -6,7 +6,7 @@ import { isControllerAliasOutpostId } from "./admission-host.js";
|
|
|
6
6
|
import { LEAD_REGISTRY_REL, validateOutpostLeadRegistry } from "./autopilot-registry.js";
|
|
7
7
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
8
8
|
import { LanesError } from "./errors.js";
|
|
9
|
-
import { LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG, LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_FLAG, LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG as PER_LEAD_ERRORS_FLAG, } from "./flag-keys.js";
|
|
9
|
+
import { LANE_AUTOPILOT_MISSED_DEADLINE_ESCALATION_FLAG, LANE_AUTOPILOT_NUDGE_DEDUPE_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG, LEAD_AUTOPILOT_KPI_STALENESS_FLAG, LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG, LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG as PER_LEAD_ERRORS_FLAG, } from "./flag-keys.js";
|
|
10
10
|
import { resolveLaneCompany } from "./lane-company.js";
|
|
11
11
|
import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
12
12
|
import { readAllLanes } from "./store.js";
|
|
@@ -24,6 +24,7 @@ export const LEAD_AUTOPILOT_REVIEW_DELAY_MS = 15 * 60_000;
|
|
|
24
24
|
export const LEAD_AUTOPILOT_FILL_DELAY_MS = 15 * 60_000;
|
|
25
25
|
export const LEAD_AUTOPILOT_PROMPT_LOOP = "personal/knowledge/team-lead-operating-loop.md";
|
|
26
26
|
export const LEAD_AUTOPILOT_KPI_STALE_AFTER_MINUTES = 55;
|
|
27
|
+
export const LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES = 55;
|
|
27
28
|
export const LEAD_AUTOPILOT_PER_LEAD_ERRORS_FLAG = PER_LEAD_ERRORS_FLAG;
|
|
28
29
|
export const LEAD_AUTOPILOT_PER_LEAD_ERRORS_DEFAULT_ENABLED = false;
|
|
29
30
|
const LIVE_STATES = new Set(["running", "queued", "awaiting_input", "blocked"]);
|
|
@@ -414,6 +415,33 @@ function managerKpiFreshness(status, now) {
|
|
|
414
415
|
};
|
|
415
416
|
});
|
|
416
417
|
}
|
|
418
|
+
function managerHeadlineFreshness(status, now) {
|
|
419
|
+
let field;
|
|
420
|
+
let rawTimestamp;
|
|
421
|
+
if (typeof status.headline_at === "string" && status.headline_at.trim()) {
|
|
422
|
+
field = "headline_at";
|
|
423
|
+
rawTimestamp = status.headline_at;
|
|
424
|
+
}
|
|
425
|
+
else if (status.headline_at === undefined || status.headline_at === null) {
|
|
426
|
+
if (typeof status.now !== "string")
|
|
427
|
+
return undefined;
|
|
428
|
+
const prefix = status.now.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))/)?.[1];
|
|
429
|
+
if (!prefix)
|
|
430
|
+
return undefined;
|
|
431
|
+
field = "now";
|
|
432
|
+
rawTimestamp = prefix;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
const timestamp = Date.parse(rawTimestamp);
|
|
438
|
+
if (!Number.isFinite(timestamp))
|
|
439
|
+
return undefined;
|
|
440
|
+
const ageMinutes = Math.floor(Math.max(0, now.getTime() - timestamp) / 60_000);
|
|
441
|
+
if (now.getTime() - timestamp <= LEAD_AUTOPILOT_HEADLINE_STALE_AFTER_MINUTES * 60_000)
|
|
442
|
+
return undefined;
|
|
443
|
+
return { field, measuredAt: new Date(timestamp).toISOString(), ageMinutes };
|
|
444
|
+
}
|
|
417
445
|
function leastMoved(lines, priorLines = []) {
|
|
418
446
|
const priorById = new Map(priorLines.map((line) => [line.id, line]));
|
|
419
447
|
return [...lines].sort((a, b) => {
|
|
@@ -431,6 +459,19 @@ export function planLeadAutopilotWakes(input) {
|
|
|
431
459
|
const blockers = statusBlockers(status);
|
|
432
460
|
const byStory = new Map(stories.map((story) => [story.id, story]));
|
|
433
461
|
const wakes = [];
|
|
462
|
+
if (input.headlineStalenessEnabled && !(lead.story_status === "done" && lead.story_complete === true)) {
|
|
463
|
+
const headline = managerHeadlineFreshness(status, now);
|
|
464
|
+
if (headline) {
|
|
465
|
+
const fingerprint = `stale-headline:${createHash("sha256").update(JSON.stringify([headline.field, headline.measuredAt])).digest("hex")}`;
|
|
466
|
+
wakes.push({
|
|
467
|
+
condition: "stale_headline",
|
|
468
|
+
lead_lane: lead.lane_id,
|
|
469
|
+
story_id: "headline-staleness",
|
|
470
|
+
fingerprint,
|
|
471
|
+
prompt: `The dashboard ${headline.field} field is ${headline.ageMinutes} minutes old. Rewrite headline, headline_at, and now in manager-status.json so the team status is current. Follow ${LEAD_AUTOPILOT_PROMPT_LOOP}.`,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
434
475
|
if (input.kpiStalenessEnabled) {
|
|
435
476
|
const staleKpis = managerKpiFreshness(status, now).filter((kpi) => kpi.stale);
|
|
436
477
|
if (input.kpiStalenessBatchEnabled && staleKpis.length > 0) {
|
|
@@ -748,6 +789,7 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
748
789
|
const status = readStatus(hqRoot, registered.status, company.slug);
|
|
749
790
|
const kpiStalenessEnabled = await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_KPI_STALENESS_FLAG, { company });
|
|
750
791
|
const kpiStalenessBatchEnabled = kpiStalenessEnabled && await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG, { company });
|
|
792
|
+
const headlineStalenessEnabled = await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG, { company });
|
|
751
793
|
if (kpiStalenessEnabled) {
|
|
752
794
|
result.kpi_status ??= [];
|
|
753
795
|
result.kpi_status.push(...managerKpiFreshness(status, now).map((kpi) => ({
|
|
@@ -772,7 +814,7 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
772
814
|
if (nudgeDedupeEnabled && pruneDeliveredIds(state, now))
|
|
773
815
|
stateChanged = true;
|
|
774
816
|
currentInputPath = registered.status;
|
|
775
|
-
|
|
817
|
+
let planned = planLeadAutopilotWakes({
|
|
776
818
|
lead,
|
|
777
819
|
children,
|
|
778
820
|
stories,
|
|
@@ -780,10 +822,26 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
780
822
|
managerStatus: status,
|
|
781
823
|
kpiStalenessEnabled,
|
|
782
824
|
kpiStalenessBatchEnabled,
|
|
825
|
+
headlineStalenessEnabled,
|
|
783
826
|
keepAliveResumed: input.keepAliveResumedLeads?.has(lead.lane_id) === true,
|
|
784
827
|
});
|
|
828
|
+
if (kpiStalenessBatchEnabled) {
|
|
829
|
+
const batch = planned.find((wake) => wake.condition === "stale_kpi" && wake.kpi_id === "batch");
|
|
830
|
+
const headline = planned.find((wake) => wake.condition === "stale_headline");
|
|
831
|
+
if (batch && headline) {
|
|
832
|
+
const priorHeadline = state.sent[`${lead.lane_id}:stale_headline`];
|
|
833
|
+
if (priorHeadline?.fingerprint !== headline.fingerprint) {
|
|
834
|
+
batch.prompt += `\n\n${headline.prompt}`;
|
|
835
|
+
batch.headline_fingerprint = headline.fingerprint;
|
|
836
|
+
}
|
|
837
|
+
planned = planned.filter((wake) => wake !== headline);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
785
840
|
const stateKey = (wake) => `${lead.lane_id}:${wake.condition}${wake.condition === "stale_kpi" ? `:${wake.kpi_id}` : ""}`;
|
|
786
|
-
const activeKeys = new Set(planned.
|
|
841
|
+
const activeKeys = new Set(planned.flatMap((wake) => [
|
|
842
|
+
stateKey(wake),
|
|
843
|
+
...(wake.headline_fingerprint ? [`${lead.lane_id}:stale_headline`] : []),
|
|
844
|
+
]));
|
|
787
845
|
const suppressedKeys = new Set();
|
|
788
846
|
for (const key of Object.keys(state.sent)) {
|
|
789
847
|
if (!key.startsWith(`${lead.lane_id}:`))
|
|
@@ -890,6 +948,8 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
890
948
|
// repeated wake/escalation cadence used by other conditions.
|
|
891
949
|
if (wake.condition === "stale_kpi")
|
|
892
950
|
continue;
|
|
951
|
+
if (wake.condition === "stale_headline")
|
|
952
|
+
continue;
|
|
893
953
|
const redeliveryDelay = nudgeDedupeEnabled ? LEAD_AUTOPILOT_NUDGE_REDELIVERY_DELAY_MS : LEAD_AUTOPILOT_FILL_DELAY_MS;
|
|
894
954
|
if (!current.acted && !current.auto_started && current.escalated_at === undefined && now.getTime() - lastWakeAt >= redeliveryDelay) {
|
|
895
955
|
const wakeCount = current.wake_count ?? 1;
|
|
@@ -931,6 +991,15 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
931
991
|
wake_count: 1,
|
|
932
992
|
last_wake_at: now.toISOString(),
|
|
933
993
|
};
|
|
994
|
+
if (wake.headline_fingerprint) {
|
|
995
|
+
state.sent[`${lead.lane_id}:stale_headline`] = {
|
|
996
|
+
fingerprint: wake.headline_fingerprint,
|
|
997
|
+
at: now.toISOString(),
|
|
998
|
+
...(statusUpdatedAt ? { status_updated_at: statusUpdatedAt } : {}),
|
|
999
|
+
wake_count: 1,
|
|
1000
|
+
last_wake_at: now.toISOString(),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
934
1003
|
stateChanged = true;
|
|
935
1004
|
if (!delivered)
|
|
936
1005
|
continue;
|
|
@@ -3,10 +3,8 @@ import { type ReadLanesFlagOptions } from "./registry-flag.js";
|
|
|
3
3
|
import type { LaneRecord, LanesConfig } from "./types.js";
|
|
4
4
|
export declare const LOW_DISK_GUARD_FLAG = "lanes.low-disk-guard";
|
|
5
5
|
export declare const CODEX_ROLLOUT_RETENTION_FLAG = "lanes.codex-rollout-retention";
|
|
6
|
-
export declare const DEFAULT_LOW_DISK_FREE_BYTES: number;
|
|
7
|
-
export declare const DEFAULT_LOW_DISK_FRACTION = 0.02;
|
|
8
6
|
export declare function lowDiskGuardEnabledForLane(hqRoot: string, lane: LaneRecord, readFlag?: (key: string, options: ReadLanesFlagOptions) => Promise<boolean>): Promise<boolean>;
|
|
9
|
-
|
|
7
|
+
interface DiskStats {
|
|
10
8
|
bsize: number;
|
|
11
9
|
blocks: number;
|
|
12
10
|
bavail: number;
|
|
@@ -25,7 +23,7 @@ export declare function checkLaneDiskSpace(hqRoot: string, providerHome: string,
|
|
|
25
23
|
hq: DiskSpaceMeasurement;
|
|
26
24
|
provider: DiskSpaceMeasurement;
|
|
27
25
|
};
|
|
28
|
-
|
|
26
|
+
interface RolloutLane {
|
|
29
27
|
provider: string;
|
|
30
28
|
state: string;
|
|
31
29
|
provider_session_id?: string;
|
|
@@ -63,4 +61,5 @@ export interface RolloutRetentionResult {
|
|
|
63
61
|
export declare function configuredCodexRolloutHomes(hqRoot: string, config: LanesConfig, hostname: string): string[];
|
|
64
62
|
/** Operates only on rollout files under configured Codex `sessions/` trees. */
|
|
65
63
|
export declare function retainCodexRollouts(options: RolloutRetentionOptions): Promise<RolloutRetentionResult>;
|
|
64
|
+
export {};
|
|
66
65
|
//# sourceMappingURL=disk-guard.d.ts.map
|
|
@@ -9,8 +9,8 @@ import { effectiveAccountHome } from "./account-home.js";
|
|
|
9
9
|
import { lanesConfigPath } from "./config.js";
|
|
10
10
|
export const LOW_DISK_GUARD_FLAG = "lanes.low-disk-guard";
|
|
11
11
|
export const CODEX_ROLLOUT_RETENTION_FLAG = "lanes.codex-rollout-retention";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const DEFAULT_LOW_DISK_FREE_BYTES = 5 * 1024 ** 3;
|
|
13
|
+
const DEFAULT_LOW_DISK_FRACTION = 0.02;
|
|
14
14
|
const EXCLUDED_SESSION_FILES = new Set(["auth.json", ".credentials.json", "thread_history.sqlite"]);
|
|
15
15
|
export async function lowDiskGuardEnabledForLane(hqRoot, lane, readFlag = readLanesRegistryFlag) {
|
|
16
16
|
const company = resolveLaneCompany(hqRoot, lane).company;
|
|
@@ -19,4 +19,7 @@ export declare const LEAD_AUTOPILOT_PER_LEAD_ERRORS_DEFAULT_ENABLED = false;
|
|
|
19
19
|
/** Batches stale manager-status KPI notices into one lead message per pass. */
|
|
20
20
|
export declare const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG = "lanes.autopilot-kpi-staleness-batch";
|
|
21
21
|
export declare const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_DEFAULT_ENABLED = false;
|
|
22
|
+
/** Enables staleness notices for a registered lead's dashboard headline. */
|
|
23
|
+
export declare const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
|
|
24
|
+
export declare const LEAD_AUTOPILOT_HEADLINE_STALENESS_DEFAULT_ENABLED = false;
|
|
22
25
|
//# sourceMappingURL=flag-keys.d.ts.map
|
|
@@ -19,4 +19,7 @@ export const LEAD_AUTOPILOT_PER_LEAD_ERRORS_DEFAULT_ENABLED = false;
|
|
|
19
19
|
/** Batches stale manager-status KPI notices into one lead message per pass. */
|
|
20
20
|
export const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_FLAG = "lanes.autopilot-kpi-staleness-batch";
|
|
21
21
|
export const LEAD_AUTOPILOT_KPI_STALENESS_BATCH_DEFAULT_ENABLED = false;
|
|
22
|
+
/** Enables staleness notices for a registered lead's dashboard headline. */
|
|
23
|
+
export const LEAD_AUTOPILOT_HEADLINE_STALENESS_FLAG = "lanes.autopilot-headline-staleness";
|
|
24
|
+
export const LEAD_AUTOPILOT_HEADLINE_STALENESS_DEFAULT_ENABLED = false;
|
|
22
25
|
//# sourceMappingURL=flag-keys.js.map
|