@indigoai-us/hq-cli 5.300.0 → 5.302.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 +42 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
- package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
- package/dist/command-catalog.generated.d.ts +32 -3
- package/dist/command-catalog.generated.js +39 -3
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/bot.js +1 -0
- package/dist/commands/cloud-provision.js +51 -2
- package/dist/commands/core-checkpoint.js +1 -0
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-handoff-fast.js +1 -0
- package/dist/commands/core-handoff.js +2 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +2 -4
- package/dist/commands/core-job-native.js +6 -10
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core.js +26 -8
- package/dist/commands/deploy-comments.js +5 -2
- package/dist/commands/files.js +31 -14
- package/dist/commands/integrations-api.d.ts +6 -6
- package/dist/commands/integrations-api.js +4 -13
- package/dist/commands/integrations-connect.d.ts +3 -0
- package/dist/commands/integrations-connect.js +21 -2
- package/dist/commands/integrations-manage.js +1 -4
- package/dist/commands/lanes-message.js +43 -5
- package/dist/commands/lanes-questions.d.ts +4 -2
- package/dist/commands/lanes-questions.js +123 -2
- package/dist/commands/lanes-resume.js +25 -4
- package/dist/commands/lanes-shared.d.ts +15 -1
- package/dist/commands/lanes-watch.js +5 -1
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +92 -9
- package/dist/commands/meetings.js +35 -16
- package/dist/commands/monitor.js +17 -2
- package/dist/commands/report.js +5 -2
- package/dist/commands/rescue.d.ts +4 -0
- package/dist/commands/rescue.js +27 -17
- package/dist/commands/safe-extract.d.ts +4 -4
- package/dist/commands/safe-extract.js +6 -7
- package/dist/lib/agent-kit/fallback.d.ts +1 -0
- package/dist/lib/agent-kit/fallback.js +1 -0
- package/dist/lib/bot/daemon.d.ts +5 -1
- package/dist/lib/bot/daemon.js +4 -4
- package/dist/lib/core-utils/soft-timeout.js +1 -1
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/deploy-apps.d.ts +13 -0
- package/dist/lib/deploy-apps.js +24 -0
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/projects.js +82 -5
- package/dist/lib/index-render/shared.d.ts +1 -1
- package/dist/lib/index-render/shared.js +4 -2
- package/dist/lib/lanes/delivery.d.ts +7 -3
- package/dist/lib/lanes/delivery.js +18 -8
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +13 -3
- package/dist/lib/lanes/keep-alive.js +29 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
- package/dist/lib/lanes/outpost-dispatch.js +12 -0
- package/dist/lib/lanes/reconcile.d.ts +1 -0
- package/dist/lib/lanes/reconcile.js +358 -38
- package/dist/lib/lanes/spawn.d.ts +1 -0
- package/dist/lib/lanes/spawn.js +20 -10
- package/dist/lib/lanes/store.js +24 -0
- package/dist/lib/lanes/story-ledger.d.ts +2 -0
- package/dist/lib/lanes/story-ledger.js +8 -0
- package/dist/lib/lanes/types.d.ts +26 -4
- package/dist/lib/lanes/types.js +33 -1
- package/dist/lib/lanes/watch.d.ts +1 -0
- package/dist/lib/lanes/watch.js +19 -6
- package/dist/lib/lanes/watcher.js +139 -9
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/run.js +36 -2
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
- package/dist/lib/search-index/background.d.ts +1 -0
- package/dist/lib/search-index/background.js +1 -0
- package/dist/utils/self-update.js +51 -4
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/update-command-supervisor.cjs +54 -1
- package/dist/utils/version-gate.d.ts +8 -0
- package/dist/utils/version-gate.js +83 -16
- package/package.json +2 -2
- package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
- package/dist/lib/install-path-compatibility-flag.js +0 -12
|
@@ -120,7 +120,7 @@ function comparison(projectDirs, before, after, minHours, json, stdout) {
|
|
|
120
120
|
/** Print token usage totals from Claude session JSONL files. */
|
|
121
121
|
export function tokenUsageReport(args = [], options = {}) {
|
|
122
122
|
const { stdout, stderr } = ioFor(options);
|
|
123
|
-
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0;
|
|
123
|
+
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0, projectDir = options.projectDir, projectDirSeen = false;
|
|
124
124
|
for (let index = 0; index < args.length;) {
|
|
125
125
|
const arg = args[index];
|
|
126
126
|
if (arg === "--last") {
|
|
@@ -149,8 +149,22 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
149
149
|
minHours = Number.parseFloat(args[index + 1] ?? "");
|
|
150
150
|
index += 2;
|
|
151
151
|
}
|
|
152
|
+
else if (arg === "--project-dir") {
|
|
153
|
+
const value = args[index + 1];
|
|
154
|
+
if (!value || value.startsWith("--")) {
|
|
155
|
+
line(stderr, "--project-dir requires a path");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
if (projectDirSeen) {
|
|
159
|
+
line(stderr, "--project-dir may be specified only once");
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
projectDir = value;
|
|
163
|
+
projectDirSeen = true;
|
|
164
|
+
index += 2;
|
|
165
|
+
}
|
|
152
166
|
else if (arg === "-h" || arg === "--help") {
|
|
153
|
-
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
167
|
+
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\n scripts/token-usage-report.sh --project-dir <path> # use one Claude activity folder\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
154
168
|
return 0;
|
|
155
169
|
}
|
|
156
170
|
else {
|
|
@@ -158,7 +172,7 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
158
172
|
return 1;
|
|
159
173
|
}
|
|
160
174
|
}
|
|
161
|
-
const projectDirs = resolveProjectDirs(options).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
175
|
+
const projectDirs = resolveProjectDirs({ ...options, projectDir }).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
162
176
|
if (before && after) {
|
|
163
177
|
comparison(projectDirs, before, after, minHours, json, stdout);
|
|
164
178
|
return 0;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Pagination for org-scoped hq-deploy app listings. */
|
|
2
|
+
export declare const MAX_DEPLOY_APPS_PAGES = 100;
|
|
3
|
+
export interface DeployAppsPage<T> {
|
|
4
|
+
apps?: unknown;
|
|
5
|
+
nextCursor?: unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Collect every page from GET /api/apps. Older hq-deploy versions omit
|
|
9
|
+
* nextCursor, which remains a valid single-page response. The cap prevents a
|
|
10
|
+
* bad cursor response from creating an unbounded CLI request loop.
|
|
11
|
+
*/
|
|
12
|
+
export declare function listDeployApps<T>(fetchPage: (cursor?: string) => Promise<DeployAppsPage<T>>): Promise<T[]>;
|
|
13
|
+
//# sourceMappingURL=deploy-apps.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Pagination for org-scoped hq-deploy app listings. */
|
|
2
|
+
export const MAX_DEPLOY_APPS_PAGES = 100;
|
|
3
|
+
/**
|
|
4
|
+
* Collect every page from GET /api/apps. Older hq-deploy versions omit
|
|
5
|
+
* nextCursor, which remains a valid single-page response. The cap prevents a
|
|
6
|
+
* bad cursor response from creating an unbounded CLI request loop.
|
|
7
|
+
*/
|
|
8
|
+
export async function listDeployApps(fetchPage) {
|
|
9
|
+
const apps = [];
|
|
10
|
+
let cursor;
|
|
11
|
+
for (let page = 0; page < MAX_DEPLOY_APPS_PAGES; page += 1) {
|
|
12
|
+
const result = await fetchPage(cursor);
|
|
13
|
+
if (Array.isArray(result.apps))
|
|
14
|
+
apps.push(...result.apps);
|
|
15
|
+
if (result.nextCursor === undefined || result.nextCursor === null)
|
|
16
|
+
return apps;
|
|
17
|
+
if (typeof result.nextCursor !== "string" || result.nextCursor.length === 0) {
|
|
18
|
+
throw new Error("hq-deploy returned an invalid apps-list cursor.");
|
|
19
|
+
}
|
|
20
|
+
cursor = result.nextCursor;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`hq-deploy apps list exceeded the ${MAX_DEPLOY_APPS_PAGES}-page safety cap.`);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=deploy-apps.js.map
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { RenderContext, RenderResult } from "./shared.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class CompanyIndexUserError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function renderCompanies(context: RenderContext, args?: string[]): RenderResult;
|
|
3
6
|
//# sourceMappingURL=companies.d.ts.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { basename, countImmediate, date, exists, immediateEntries, isHidden, log, projectStatus, readJson, sanitize, titleize, truncate, write } from "./shared.js";
|
|
4
|
+
export class CompanyIndexUserError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CompanyIndexUserError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
function description(root, company, name) {
|
|
5
11
|
const base = `companies/${company}/${name}`;
|
|
6
12
|
const nonHidden = (child) => !isHidden(child);
|
|
@@ -17,9 +23,66 @@ function description(root, company, name) {
|
|
|
17
23
|
default: return "—";
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
|
-
export function renderCompanies(context) {
|
|
26
|
+
export function renderCompanies(context, args = []) {
|
|
27
|
+
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
28
|
+
(context.output ?? ((message) => process.stdout.write(message)))("Usage: hq core rebuild-index companies [--company <slug>]\n\n" +
|
|
29
|
+
"Regenerate company root INDEX.md files. Without --company, all companies are regenerated.\n" +
|
|
30
|
+
"--company <slug> regenerates only that company's index.\n" +
|
|
31
|
+
"Company-scoped writes require the cli.rebuild-index-company-scope flag.\n");
|
|
32
|
+
return { written: [] };
|
|
33
|
+
}
|
|
34
|
+
let selectedCompany;
|
|
35
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
36
|
+
const arg = args[index];
|
|
37
|
+
if (arg === "--company") {
|
|
38
|
+
if (selectedCompany !== undefined)
|
|
39
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
40
|
+
const value = args[index + 1];
|
|
41
|
+
if (!value || value.startsWith("--"))
|
|
42
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
43
|
+
selectedCompany = value;
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
else if (arg.startsWith("--company=")) {
|
|
47
|
+
if (selectedCompany !== undefined)
|
|
48
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
49
|
+
selectedCompany = arg.slice("--company=".length);
|
|
50
|
+
if (!selectedCompany)
|
|
51
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw new CompanyIndexUserError(`rebuild-index companies: unknown option ${arg}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
21
57
|
const written = [];
|
|
22
|
-
|
|
58
|
+
let scopedDirectories;
|
|
59
|
+
if (selectedCompany === undefined) {
|
|
60
|
+
scopedDirectories = immediateEntries(context.root, "companies", "dir");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (selectedCompany === "." || selectedCompany === ".." ||
|
|
64
|
+
selectedCompany.includes("/") || selectedCompany.includes("\\") ||
|
|
65
|
+
path.basename(selectedCompany) !== selectedCompany ||
|
|
66
|
+
selectedCompany.startsWith("_") || isHidden(selectedCompany)) {
|
|
67
|
+
throw new CompanyIndexUserError(`rebuild-index companies: invalid company slug "${selectedCompany}"`);
|
|
68
|
+
}
|
|
69
|
+
const companiesRoot = path.resolve(context.root, "companies");
|
|
70
|
+
const selectedDirectory = path.resolve(companiesRoot, selectedCompany);
|
|
71
|
+
let selectedStat;
|
|
72
|
+
try {
|
|
73
|
+
selectedStat = fs.lstatSync(selectedDirectory);
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
if (cause.code !== "ENOENT")
|
|
77
|
+
throw cause;
|
|
78
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
79
|
+
}
|
|
80
|
+
if (!selectedStat.isDirectory()) {
|
|
81
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
82
|
+
}
|
|
83
|
+
scopedDirectories = [selectedDirectory];
|
|
84
|
+
}
|
|
85
|
+
for (const directory of scopedDirectories) {
|
|
23
86
|
const company = basename(directory);
|
|
24
87
|
if (company.startsWith("_") || isHidden(company))
|
|
25
88
|
continue;
|
|
@@ -40,7 +103,7 @@ export function renderCompanies(context) {
|
|
|
40
103
|
const parsed = readJson(prd);
|
|
41
104
|
let text = typeof parsed?.description === "string" ? parsed.description : "";
|
|
42
105
|
text = truncate(sanitize(text), 110) || "—";
|
|
43
|
-
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active")} | ${text} |`);
|
|
106
|
+
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active", prd)} | ${text} |`);
|
|
44
107
|
}
|
|
45
108
|
}
|
|
46
109
|
else
|
|
@@ -2,4 +2,5 @@ import { RenderContext, RenderResult } from "./shared.js";
|
|
|
2
2
|
export type IndexTarget = "all" | "companies" | "company-knowledge" | "orchestrator" | "projects" | "public-knowledge" | "reports" | "social-drafts" | "threads" | "workers";
|
|
3
3
|
export declare function renderIndexTarget(target: IndexTarget, context: RenderContext, args?: string[]): RenderResult;
|
|
4
4
|
export { atomicWrite, comparePath, immediateEntries, type RenderContext, type RenderResult } from "./shared.js";
|
|
5
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,7 +10,7 @@ import { renderWorkers } from "./workers.js";
|
|
|
10
10
|
const allOrder = ["threads", "orchestrator", "companies", "projects", "company-knowledge", "public-knowledge", "workers", "reports", "social-drafts"];
|
|
11
11
|
export function renderIndexTarget(target, context, args = []) {
|
|
12
12
|
switch (target) {
|
|
13
|
-
case "companies": return renderCompanies(context);
|
|
13
|
+
case "companies": return renderCompanies(context, args);
|
|
14
14
|
case "company-knowledge": return renderCompanyKnowledge(context);
|
|
15
15
|
case "orchestrator": return renderOrchestrator(context);
|
|
16
16
|
case "projects": return renderProjects(context);
|
|
@@ -40,4 +40,5 @@ export function renderIndexTarget(target, context, args = []) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
export { atomicWrite, comparePath, immediateEntries } from "./shared.js";
|
|
43
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
43
44
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,92 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
1
2
|
import * as path from "path";
|
|
2
|
-
import { basename, date, exists, immediateEntries, isHidden, log, projectStatus, readJson, sanitize, truncate, write } from "./shared.js";
|
|
3
|
+
import { at, basename, date, exists, extractKeepFences, generatedFingerprint, generatedStamp, hasUnclosedKeepMarker, immediateEntries, isHidden, log, looksAutoGenerated, projectStatus, readJson, readText, sanitize, stampGenerated, titleize, truncate, write, } from "./shared.js";
|
|
4
|
+
function composeCompanyProjectsIndex(raw, existing) {
|
|
5
|
+
const stamped = stampGenerated(raw);
|
|
6
|
+
const fences = existing === undefined ? "" : extractKeepFences(existing);
|
|
7
|
+
if (!fences)
|
|
8
|
+
return stamped;
|
|
9
|
+
return `${stamped.replace(/\n+$/, "\n")}\n${fences}\n`;
|
|
10
|
+
}
|
|
11
|
+
function shouldOverwriteCompanyProjectsIndex(context, existing, next) {
|
|
12
|
+
if (existing === undefined)
|
|
13
|
+
return "write";
|
|
14
|
+
if (context.force)
|
|
15
|
+
return "write";
|
|
16
|
+
if (hasUnclosedKeepMarker(existing))
|
|
17
|
+
return "skip";
|
|
18
|
+
const stored = generatedStamp(existing);
|
|
19
|
+
if (stored && stored === generatedFingerprint(existing))
|
|
20
|
+
return "write";
|
|
21
|
+
if (stored)
|
|
22
|
+
return "refuse";
|
|
23
|
+
if (looksAutoGenerated(existing))
|
|
24
|
+
return "write";
|
|
25
|
+
if (generatedFingerprint(existing) === generatedFingerprint(next))
|
|
26
|
+
return "write";
|
|
27
|
+
return "refuse";
|
|
28
|
+
}
|
|
29
|
+
function renderCompanyProjects(context, company, projectRoot) {
|
|
30
|
+
const written = [];
|
|
31
|
+
const lines = [`# ${titleize(company)} Projects`, "", `> Auto-generated. Updated: ${date(context.now)}`, "", "| Project | Status | Description |", "|---------|--------|-------------|"];
|
|
32
|
+
for (const directory of immediateEntries(context.root, projectRoot, "dir")) {
|
|
33
|
+
const name = basename(directory);
|
|
34
|
+
if (name.startsWith("_") || isHidden(name))
|
|
35
|
+
continue;
|
|
36
|
+
const prd = path.join(directory, "prd.json");
|
|
37
|
+
const parsed = readJson(prd);
|
|
38
|
+
const description = truncate(sanitize(typeof parsed?.description === "string" ? parsed.description : ""), 110) || "—";
|
|
39
|
+
lines.push(`| \`${name}\` | ${projectStatus(context.root, name, prd, "active", prd)} | ${description} |`);
|
|
40
|
+
}
|
|
41
|
+
lines.push("");
|
|
42
|
+
const output = `${projectRoot}/INDEX.md`;
|
|
43
|
+
const existing = readText(at(context.root, output));
|
|
44
|
+
// An unreadable existing file is not an invitation to replace it.
|
|
45
|
+
if (existing === undefined && fs.existsSync(at(context.root, output))) {
|
|
46
|
+
context.refused?.push(output);
|
|
47
|
+
log(context, `rebuild-projects-index: refused to overwrite unreadable ${output}`);
|
|
48
|
+
return written;
|
|
49
|
+
}
|
|
50
|
+
const next = composeCompanyProjectsIndex(lines.join("\n"), existing);
|
|
51
|
+
const decision = shouldOverwriteCompanyProjectsIndex(context, existing, next);
|
|
52
|
+
if (decision === "skip") {
|
|
53
|
+
log(context, `rebuild-projects-index: skipped ${output} (<!-- hq:keep -->)`);
|
|
54
|
+
return written;
|
|
55
|
+
}
|
|
56
|
+
if (decision === "refuse") {
|
|
57
|
+
context.refused?.push(output);
|
|
58
|
+
log(context, `rebuild-projects-index: refused ${output} (hand-written or edited; differs from last generated output)`);
|
|
59
|
+
log(context, "Re-run with --accept to overwrite, or add <!-- hq:keep --> to opt out.");
|
|
60
|
+
return written;
|
|
61
|
+
}
|
|
62
|
+
write(context, output, next);
|
|
63
|
+
written.push(output);
|
|
64
|
+
log(context, `rebuild-projects-index: wrote ${output}`);
|
|
65
|
+
return written;
|
|
66
|
+
}
|
|
3
67
|
export function renderProjects(context) {
|
|
68
|
+
const written = [];
|
|
69
|
+
const companyDirectories = immediateEntries(context.root, "companies", "dir");
|
|
70
|
+
for (const companyDirectory of companyDirectories) {
|
|
71
|
+
const company = basename(companyDirectory);
|
|
72
|
+
if (company.startsWith("_") || isHidden(company))
|
|
73
|
+
continue;
|
|
74
|
+
const projectRoot = ["companies", company, "projects"].join("/");
|
|
75
|
+
if (immediateEntries(context.root, projectRoot, "dir").length > 0 || fs.existsSync(at(context.root, projectRoot))) {
|
|
76
|
+
written.push(...renderCompanyProjects(context, company, projectRoot));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
4
79
|
const base = "personal/projects";
|
|
5
80
|
if (!exists(context.root, base)) {
|
|
6
81
|
log(context, "rebuild-projects-index: personal/projects/ missing, skipping");
|
|
7
|
-
return { written
|
|
82
|
+
return { written };
|
|
8
83
|
}
|
|
9
84
|
const lines = ["# Projects", "", `> Auto-generated. Updated: ${date(context.now)}`, "", "Personal/HQ projects only. Company-scoped projects live in `companies/{co}/projects/`.", "", "## Company Project Directories", "", "| Company | Path |", "|---------|------|"];
|
|
10
|
-
for (const companyDirectory of
|
|
85
|
+
for (const companyDirectory of companyDirectories) {
|
|
11
86
|
const company = basename(companyDirectory);
|
|
12
|
-
if (!company.startsWith("_") && !isHidden(company) &&
|
|
87
|
+
if (!company.startsWith("_") && !isHidden(company) && fs.existsSync(at(context.root, ["companies", company, "projects"].join("/")))) {
|
|
13
88
|
lines.push(`| ${company} | \`companies/${company}/projects/\` |`);
|
|
89
|
+
}
|
|
14
90
|
}
|
|
15
91
|
lines.push("", "## Personal/HQ Projects", "", "Projects here are HQ infrastructure or cross-company tools.", "", "| Project | Stories | Status | Description |", "|---------|---------|--------|-------------|");
|
|
16
92
|
const projects = immediateEntries(context.root, base, "dir");
|
|
@@ -27,7 +103,8 @@ export function renderProjects(context) {
|
|
|
27
103
|
lines.push("");
|
|
28
104
|
const output = `${base}/INDEX.md`;
|
|
29
105
|
write(context, output, lines.join("\n"));
|
|
106
|
+
written.push(output);
|
|
30
107
|
log(context, `rebuild-projects-index: wrote ${output} (${projects.length} HQ project(s))`);
|
|
31
|
-
return { written
|
|
108
|
+
return { written };
|
|
32
109
|
}
|
|
33
110
|
//# sourceMappingURL=projects.js.map
|
|
@@ -39,7 +39,7 @@ export declare function hasUnclosedKeepMarker(content: string): boolean;
|
|
|
39
39
|
export declare function looksAutoGenerated(content: string): boolean;
|
|
40
40
|
export declare function generatedStamp(content: string): string | undefined;
|
|
41
41
|
export declare function log(context: RenderContext, message: string): void;
|
|
42
|
-
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string): string;
|
|
42
|
+
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string, expectedStatePrdPath?: string): string;
|
|
43
43
|
export declare function basename(file: string): string;
|
|
44
44
|
export declare function isHidden(name: string): boolean;
|
|
45
45
|
export declare function mtime(file: string): number;
|
|
@@ -134,9 +134,11 @@ export function generatedStamp(content) {
|
|
|
134
134
|
return content.match(/^<!-- hq-generated-sha256: ([a-f0-9]{64}) -->/m)?.[1];
|
|
135
135
|
}
|
|
136
136
|
export function log(context, message) { context.log?.(message); }
|
|
137
|
-
export function projectStatus(root, project, prdPath, fallback) {
|
|
137
|
+
export function projectStatus(root, project, prdPath, fallback, expectedStatePrdPath) {
|
|
138
138
|
const state = readJson(at(root, `workspace/orchestrator/${project}/state.json`));
|
|
139
|
-
|
|
139
|
+
const stateMatchesPrd = expectedStatePrdPath === undefined || (typeof state?.prd_path === "string" &&
|
|
140
|
+
path.resolve(root, state.prd_path) === path.resolve(root, expectedStatePrdPath));
|
|
141
|
+
if (state && stateMatchesPrd) {
|
|
140
142
|
const raw = typeof state.status === "string" ? state.status : "active";
|
|
141
143
|
if (["completed", "complete"].includes(raw))
|
|
142
144
|
return "complete";
|
|
@@ -41,8 +41,8 @@ export declare function sessionNotPortableMessage(provider: LaneProvider): strin
|
|
|
41
41
|
* is not terminal (spec §6.7) and nothing stopped the lane deliberately.
|
|
42
42
|
*
|
|
43
43
|
* Refused by `hq lanes message`:
|
|
44
|
-
* `awaiting_input` has its own path, `hq lanes questions answer`,
|
|
45
|
-
*
|
|
44
|
+
* `awaiting_input` has its own path, `hq lanes questions answer`, while a
|
|
45
|
+
* senior may resume it when no answerable pending question remains. `killed` and
|
|
46
46
|
* `cancelled` were stopped on purpose by the operator; a message must not
|
|
47
47
|
* silently restart them. `failed` means the run ended without an envelope or
|
|
48
48
|
* never started; only the lane's senior can explicitly recover it with
|
|
@@ -66,6 +66,8 @@ export type ResumeCommandState = (typeof RESUME_COMMAND_STATES)[number];
|
|
|
66
66
|
export declare function isResumeCommandState(state: LaneState): state is ResumeCommandState;
|
|
67
67
|
export declare const LANE_DELIVERY_MECHANISMS: readonly ["dropbox", "resume", "none"];
|
|
68
68
|
export type LaneDeliveryMechanism = (typeof LANE_DELIVERY_MECHANISMS)[number];
|
|
69
|
+
/** Additional presentation-only result used by `hq lanes show`. */
|
|
70
|
+
export type LaneShowDeliveryMechanism = LaneDeliveryMechanism | "questions";
|
|
69
71
|
export type DeliveryLane = Pick<LaneRecord, "state" | "provider" | "provider_session_id" | "terminal_reason" | "envelope_repair">;
|
|
70
72
|
/** Only the terminal result of a failed automatic envelope repair is recoverable. */
|
|
71
73
|
export declare function isFailedEnvelopeRepairLane(lane: DeliveryLane): boolean;
|
|
@@ -139,7 +141,8 @@ export type LaunchTrigger = {
|
|
|
139
141
|
/** `hq lanes resume`: the operator's explicit opt-in to continue an exited lane. */
|
|
140
142
|
kind: "resume";
|
|
141
143
|
/** Lane state at the moment of the command; recorded as `last_resume.resumed_from`. */
|
|
142
|
-
from: ResumeCommandState | "failed";
|
|
144
|
+
from: ResumeCommandState | "failed" | "awaiting_input";
|
|
145
|
+
allowAwaitingInput?: true;
|
|
143
146
|
fromFailed?: true;
|
|
144
147
|
/** Senior that authorized a failed-lane resume, rechecked at the claim CAS. */
|
|
145
148
|
expectedSenior?: SeniorRef;
|
|
@@ -158,6 +161,7 @@ export type LaunchPlan = {
|
|
|
158
161
|
*/
|
|
159
162
|
export declare function resumeCommandRefusal(lane: DeliveryLane, options?: {
|
|
160
163
|
allowFailed?: boolean;
|
|
164
|
+
allowAwaitingInput?: boolean;
|
|
161
165
|
}): {
|
|
162
166
|
error: "lane_not_resumable" | "resume_unsupported" | "no_provider_session";
|
|
163
167
|
message: string;
|
|
@@ -61,8 +61,8 @@ export function sessionNotPortableMessage(provider) {
|
|
|
61
61
|
* is not terminal (spec §6.7) and nothing stopped the lane deliberately.
|
|
62
62
|
*
|
|
63
63
|
* Refused by `hq lanes message`:
|
|
64
|
-
* `awaiting_input` has its own path, `hq lanes questions answer`,
|
|
65
|
-
*
|
|
64
|
+
* `awaiting_input` has its own path, `hq lanes questions answer`, while a
|
|
65
|
+
* senior may resume it when no answerable pending question remains. `killed` and
|
|
66
66
|
* `cancelled` were stopped on purpose by the operator; a message must not
|
|
67
67
|
* silently restart them. `failed` means the run ended without an envelope or
|
|
68
68
|
* never started; only the lane's senior can explicitly recover it with
|
|
@@ -104,7 +104,7 @@ export function laneDeliveryMechanism(lane) {
|
|
|
104
104
|
if (lane.state === "queued" || lane.state === "running")
|
|
105
105
|
return "dropbox";
|
|
106
106
|
if (providerSupportsSessionResume(lane.provider) &&
|
|
107
|
-
(isResumableLaneState(lane.state) || isFailedEnvelopeRepairLane(lane)) &&
|
|
107
|
+
(isResumableLaneState(lane.state) || lane.state === "awaiting_input" || isFailedEnvelopeRepairLane(lane)) &&
|
|
108
108
|
hasRecordedSessionId(lane)) {
|
|
109
109
|
return "resume";
|
|
110
110
|
}
|
|
@@ -348,6 +348,7 @@ export const LAUNCH_TRIGGERS = ["admit", "answered_questions", "message", "resum
|
|
|
348
348
|
*/
|
|
349
349
|
export function resumeCommandRefusal(lane, options = {}) {
|
|
350
350
|
if (!isResumeCommandState(lane.state) &&
|
|
351
|
+
!(options.allowAwaitingInput === true && lane.state === "awaiting_input") &&
|
|
351
352
|
!isFailedEnvelopeRepairLane(lane) &&
|
|
352
353
|
!(options.allowFailed === true && lane.state === "failed")) {
|
|
353
354
|
return {
|
|
@@ -398,7 +399,10 @@ export function planLaneLaunch(lane, trigger) {
|
|
|
398
399
|
return { mode: "resume", session_id: lane.provider_session_id };
|
|
399
400
|
}
|
|
400
401
|
case "resume": {
|
|
401
|
-
const refusal = resumeCommandRefusal(lane, {
|
|
402
|
+
const refusal = resumeCommandRefusal(lane, {
|
|
403
|
+
allowFailed: trigger.fromFailed === true,
|
|
404
|
+
allowAwaitingInput: trigger.allowAwaitingInput === true,
|
|
405
|
+
});
|
|
402
406
|
if (refusal !== null || !hasRecordedSessionId(lane)) {
|
|
403
407
|
throw new LanesError(refusal?.error ?? "no_provider_session", refusal?.message ?? `Lane is ${lane.state} and has no recorded provider session`, { state: lane.state, provider: lane.provider });
|
|
404
408
|
}
|
|
@@ -419,11 +423,17 @@ export function claimableStatesFor(trigger, lane) {
|
|
|
419
423
|
case "answered_questions":
|
|
420
424
|
return ["awaiting_input"];
|
|
421
425
|
case "message":
|
|
422
|
-
return envelopeRepairFailed
|
|
423
|
-
|
|
424
|
-
|
|
426
|
+
return envelopeRepairFailed
|
|
427
|
+
? [...RESUMABLE_LANE_STATES, "failed"]
|
|
428
|
+
: lane?.state === "awaiting_input"
|
|
429
|
+
? [...RESUMABLE_LANE_STATES, "awaiting_input"]
|
|
430
|
+
: RESUMABLE_LANE_STATES;
|
|
431
|
+
case "resume": {
|
|
432
|
+
const states = trigger.fromFailed === true || envelopeRepairFailed
|
|
425
433
|
? [...RESUME_COMMAND_STATES, "failed"]
|
|
426
|
-
: RESUME_COMMAND_STATES;
|
|
434
|
+
: [...RESUME_COMMAND_STATES];
|
|
435
|
+
return lane?.state === "awaiting_input" ? [...states, "awaiting_input"] : states;
|
|
436
|
+
}
|
|
427
437
|
default: {
|
|
428
438
|
const _exhaustive = trigger;
|
|
429
439
|
return _exhaustive;
|
|
@@ -64,7 +64,7 @@ export type { RegistryWorkerEntry, ResolvedLaneWorker, WorkerTenancy, WorkerYaml
|
|
|
64
64
|
export { bodyStoreForLane, envelopeQuestionId, parseQuestionBodyStore, questionBodyAbsPath, questionBodyRef, raiseEnvelopeQuestions, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "./questions.js";
|
|
65
65
|
export type { QuestionBody, RaiseLaneQuestionInput } from "./questions.js";
|
|
66
66
|
export { LANE_DELIVERY_MECHANISMS, LAUNCH_TRIGGERS, PROVIDER_SESSION_RESUME, PROVIDER_SESSION_PORTABLE, providerSessionPortable, sessionNotPortableMessage, parseGrokResultOutput, RESUMABLE_LANE_STATES, RESUME_COMMAND_STATES, SESSION_ID_ABSENT_REASONS, claimableStatesFor, deliveryRefusalReason, hasRecordedSessionId, isResumableLaneState, isResumeCommandState, laneDeliveryMechanism, parseClaudeResultOutput, parseCodexEventOutput, parseWorkerRunOutput, planLaneLaunch, providerSupportsSessionResume, readWorkerRunResult, resumeCommandRefusal, } from "./delivery.js";
|
|
67
|
-
export type { DeliveryLane, LaneDeliveryMechanism, LaunchPlan, LaunchTrigger, LaunchTriggerKind, ResumableLaneState, ResumeCommandState, SessionIdAbsentReason, WorkerRunResult, } from "./delivery.js";
|
|
67
|
+
export type { DeliveryLane, LaneDeliveryMechanism, LaneShowDeliveryMechanism, LaunchPlan, LaunchTrigger, LaunchTriggerKind, ResumableLaneState, ResumeCommandState, SessionIdAbsentReason, WorkerRunResult, } from "./delivery.js";
|
|
68
68
|
export { DROPBOX_DIR, INBOX_NAME, appendDropboxMessage, answerDropboxMessage, drainDropbox, dropboxDir, dropboxInboxPath, formatOperatorInstruction, hookAdditionalContextJson, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneIdForAnswerDelivery, operatorMessageRefusalReason, readToolActivity, recordToolActivity, runDrainHook, toolActivityPath, TOOL_ACTIVITY_NAME, } from "./dropbox.js";
|
|
69
69
|
export type { DrainDeps, DrainHookResult, DropboxMessage, ToolActivityRecord, } from "./dropbox.js";
|
|
70
70
|
export { DRAIN_HOOK_BASENAME, DRAIN_HOOK_REGISTRATION_KINDS, DRAIN_HOOK_TIMEOUT_SECONDS, PROVIDER_REGISTRATION_KIND, drainHookCommand, drainHookRegistrationPath, drainHookShPath, installDrainHook, lanesHookAssetsDir, mergeClaudeSettingsHook, mergeCodexConfigHook, probeDrainHookRegistration, renderDrainHookJson, } from "./hooks-install.js";
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LaneRecord, type ManagerStatusGateHistoryEntry, type QuestionRecord } from "./types.js";
|
|
2
2
|
export declare const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
3
3
|
export declare const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
4
|
+
export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG = "lanes.keep-alive-next-check-at";
|
|
5
|
+
export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
|
|
6
|
+
export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS: number;
|
|
4
7
|
export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
|
|
5
8
|
export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
|
|
6
9
|
export declare const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS: number;
|
|
@@ -35,10 +38,17 @@ export type KeepAliveBlockedReason = NonNullable<LaneRecord["keep_alive_blocked_
|
|
|
35
38
|
export interface KeepAliveDoneEnvelope {
|
|
36
39
|
decision?: string;
|
|
37
40
|
story_complete?: boolean;
|
|
41
|
+
next_check_at?: string;
|
|
38
42
|
}
|
|
39
|
-
export type DoneKeepAliveVerdict = "eligible" | "standing_idle" | "story_complete";
|
|
43
|
+
export type DoneKeepAliveVerdict = "eligible" | "standing_idle" | "story_complete" | "next_check_at";
|
|
44
|
+
export declare function keepAliveNextCheckAtMs(value: unknown, nowMs: number, anchorMs?: number): number | undefined;
|
|
40
45
|
/** Shared done-state policy used by watch, reconcile, and the show command. */
|
|
41
|
-
export declare function doneKeepAliveVerdict(lane: Pick<LaneRecord, "keep_alive_standing">, envelope: KeepAliveDoneEnvelope, hasQueuedWork: boolean
|
|
46
|
+
export declare function doneKeepAliveVerdict(lane: Pick<LaneRecord, "keep_alive_standing">, envelope: KeepAliveDoneEnvelope, hasQueuedWork: boolean, options?: {
|
|
47
|
+
nextCheckAtEnabled?: boolean;
|
|
48
|
+
hasPendingWork?: boolean;
|
|
49
|
+
nowMs?: number;
|
|
50
|
+
nextCheckAnchorMs?: number;
|
|
51
|
+
}): DoneKeepAliveVerdict | null;
|
|
42
52
|
export declare function canResumeDoneKeepAliveLane(lane: Pick<LaneRecord, "keep_alive_standing">, envelope: KeepAliveDoneEnvelope, hasQueuedWork: boolean): boolean;
|
|
43
53
|
/** Work signals already persisted for a lane that ended its turn. */
|
|
44
54
|
export declare function hasPendingKeepAliveWork(hqRoot: string, lane: Pick<LaneRecord, "lane_id" | "company" | "pending_resume">, questionRecords?: readonly QuestionRecord[]): boolean;
|
|
@@ -3,8 +3,12 @@ import * as path from "node:path";
|
|
|
3
3
|
import { LanesError } from "./errors.js";
|
|
4
4
|
import { dropboxInboxHasMessages } from "./dropbox.js";
|
|
5
5
|
import { appendCappedHistory, listQuestionIds, readQuestion, retryUpdateLane } from "./store.js";
|
|
6
|
+
import { isIsoDateTime } from "./types.js";
|
|
6
7
|
export const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
7
8
|
export const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
9
|
+
export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG = "lanes.keep-alive-next-check-at";
|
|
10
|
+
export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
|
|
11
|
+
export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS = 24 * 60 * 60 * 1000;
|
|
8
12
|
export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
|
|
9
13
|
export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
|
|
10
14
|
export const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS = 15 * 60 * 1000;
|
|
@@ -98,15 +102,35 @@ export function queueManagerStatusGateResume(hqRoot, laneId, entry, resumeText)
|
|
|
98
102
|
export const LEAD_LANE_KEEP_ALIVE_RESUME_TEXT = "Automatic keep-alive resume: your last turn ended with decision done and story_complete false. Continue the open work from where you stopped. End with done only when story_complete is true; if you are waiting, use hq lanes ask or stay in the loop.";
|
|
99
103
|
export const LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT = "You ended blocked with no pending question. Record the event you wait on in waiting_on, re-check each blocker, and continue the next unblocked story.";
|
|
100
104
|
export const LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT = "Automatic keep-alive resume: your previous turn was interrupted by a provider process exit. Check your last state before continuing. Continue the open work from where you stopped. End with done only when story_complete is true; if you are waiting, use hq lanes ask or stay in the loop.";
|
|
105
|
+
export function keepAliveNextCheckAtMs(value, nowMs, anchorMs = nowMs) {
|
|
106
|
+
if (typeof value !== "string" || !isIsoDateTime(value))
|
|
107
|
+
return undefined;
|
|
108
|
+
const parsed = Date.parse(value);
|
|
109
|
+
if (parsed <= nowMs)
|
|
110
|
+
return undefined;
|
|
111
|
+
const stableAnchorMs = Number.isFinite(anchorMs) ? anchorMs : nowMs;
|
|
112
|
+
const capped = Math.min(parsed, stableAnchorMs + LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS);
|
|
113
|
+
return capped > nowMs ? capped : undefined;
|
|
114
|
+
}
|
|
101
115
|
/** Shared done-state policy used by watch, reconcile, and the show command. */
|
|
102
|
-
export function doneKeepAliveVerdict(lane, envelope, hasQueuedWork) {
|
|
116
|
+
export function doneKeepAliveVerdict(lane, envelope, hasQueuedWork, options = {}) {
|
|
103
117
|
if (envelope.decision !== "done")
|
|
104
118
|
return null;
|
|
119
|
+
let verdict;
|
|
105
120
|
if (envelope.story_complete !== true)
|
|
106
|
-
|
|
107
|
-
if (lane.keep_alive_standing !== true)
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
verdict = "eligible";
|
|
122
|
+
else if (lane.keep_alive_standing !== true)
|
|
123
|
+
verdict = "story_complete";
|
|
124
|
+
else
|
|
125
|
+
verdict = hasQueuedWork ? "eligible" : "standing_idle";
|
|
126
|
+
if (verdict === "eligible" && options.nextCheckAtEnabled === true && options.hasPendingWork !== true) {
|
|
127
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
128
|
+
const anchorMs = options.nextCheckAnchorMs ?? nowMs;
|
|
129
|
+
const nextCheckAtMs = keepAliveNextCheckAtMs(envelope.next_check_at, nowMs, anchorMs);
|
|
130
|
+
if (nextCheckAtMs !== undefined)
|
|
131
|
+
return "next_check_at";
|
|
132
|
+
}
|
|
133
|
+
return verdict;
|
|
110
134
|
}
|
|
111
135
|
export function canResumeDoneKeepAliveLane(lane, envelope, hasQueuedWork) {
|
|
112
136
|
return doneKeepAliveVerdict(lane, envelope, hasQueuedWork) === "eligible";
|
|
@@ -105,6 +105,11 @@ export declare function buildOutpostQuestionListCommand(input?: {
|
|
|
105
105
|
}): string;
|
|
106
106
|
/** Read one question's body and owner-computed audience from its Outpost. */
|
|
107
107
|
export declare function buildOutpostQuestionShowCommand(questionId: string): string;
|
|
108
|
+
/** Cancel an owner-hosted question through the existing questions command. */
|
|
109
|
+
export declare function buildOutpostQuestionCancelCommand(input: {
|
|
110
|
+
questionId: string;
|
|
111
|
+
reason: string;
|
|
112
|
+
}): string;
|
|
108
113
|
/** Commit a queued lane move on the host that currently owns its state. */
|
|
109
114
|
export declare function buildOutpostMoveTargetCommand(input: {
|
|
110
115
|
laneId: string;
|
|
@@ -113,6 +113,18 @@ export function buildOutpostQuestionShowCommand(questionId) {
|
|
|
113
113
|
questionId,
|
|
114
114
|
]));
|
|
115
115
|
}
|
|
116
|
+
/** Cancel an owner-hosted question through the existing questions command. */
|
|
117
|
+
export function buildOutpostQuestionCancelCommand(input) {
|
|
118
|
+
return withRemoteHqDir(joinCommandParts([
|
|
119
|
+
"hq",
|
|
120
|
+
"lanes",
|
|
121
|
+
"questions",
|
|
122
|
+
"cancel",
|
|
123
|
+
input.questionId,
|
|
124
|
+
"--reason",
|
|
125
|
+
input.reason,
|
|
126
|
+
]));
|
|
127
|
+
}
|
|
116
128
|
/** Commit a queued lane move on the host that currently owns its state. */
|
|
117
129
|
export function buildOutpostMoveTargetCommand(input) {
|
|
118
130
|
return withRemoteHqDir(joinCommandParts([
|
|
@@ -24,6 +24,7 @@ export declare const RECONCILE_LOCK_TIMEOUT_MS = 5000;
|
|
|
24
24
|
/** A pickup probe that has not cleared its marker within this window is abandoned. */
|
|
25
25
|
export declare const PICKUP_VALIDATION_MAX_AGE_MS: number;
|
|
26
26
|
export declare const PICKUP_VALIDATION_TIMEOUT_REASON: "pickup_validation_timeout";
|
|
27
|
+
export declare const CLOSE_INTERRUPTED_DONE_STORY_WORKERS_FLAG = "lanes.close-interrupted-done-story-workers";
|
|
27
28
|
export declare const RECONCILE_UNIT_NAME = "hq-lanes-reconcile.service";
|
|
28
29
|
export declare const RECONCILE_TIMER_NAME = "hq-lanes-reconcile.timer";
|
|
29
30
|
export interface ReconcileSkip {
|