@indigoai-us/hq-cli 5.345.170 → 5.345.172
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 +84 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +206 -7
- package/dist/command-catalog.generated.js +252 -8
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +179 -18
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +228 -10
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/daemon.js +16 -1
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +22 -4
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-core.d.ts +22 -0
- package/dist/commands/sync-core.js +124 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2447 -2135
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +142 -0
- package/dist/lib/bot/fleet-local.js +689 -0
- package/dist/lib/bot/fleet-python.d.ts +47 -0
- package/dist/lib/bot/fleet-python.js +218 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/prompt.d.ts +6 -1
- package/dist/lib/bot/prompt.js +8 -3
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +12 -4
- package/dist/lib/daemon/catalog.js +70 -24
- package/dist/lib/daemon/config.d.ts +0 -7
- package/dist/lib/daemon/config.js +0 -32
- package/dist/lib/daemon/run.d.ts +5 -1
- package/dist/lib/daemon/run.js +48 -11
- package/dist/lib/daemon/supervisor.js +3 -1
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/sync-core/engine-home.d.ts +44 -0
- package/dist/lib/sync-core/engine-home.js +193 -0
- package/dist/lib/sync-core/flag.d.ts +17 -0
- package/dist/lib/sync-core/flag.js +47 -0
- package/dist/lib/sync-core/reconcile.d.ts +31 -0
- package/dist/lib/sync-core/reconcile.js +196 -0
- package/dist/lib/sync-core/release.d.ts +32 -0
- package/dist/lib/sync-core/release.js +140 -0
- package/dist/lib/sync-core/rollout-state.d.ts +24 -0
- package/dist/lib/sync-core/rollout-state.js +94 -0
- package/dist/lib/vault-sync-switches.d.ts +5 -0
- package/dist/lib/vault-sync-switches.js +43 -0
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/remote-list-not-converging.d.ts +21 -0
- package/dist/utils/remote-list-not-converging.js +40 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
- package/dist/lib/daemon/sync-core-canary.d.ts +0 -9
- package/dist/lib/daemon/sync-core-canary.js +0 -56
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
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
|
+
/**
|
|
4
|
+
* `all` accepts `--company <slug>` (repeatable). Without it, `all` keeps the
|
|
5
|
+
* every-company rebuild; the command layer decides whether that is allowed.
|
|
6
|
+
* Slugs are validated before any child runs, so a bad slug writes nothing.
|
|
7
|
+
*/
|
|
8
|
+
/** `all` option: run the companies child without --company (the pre-scoping behavior). */
|
|
9
|
+
export declare const UNSCOPED_COMPANIES_INDEX = "--unscoped-companies-index";
|
|
10
|
+
/** `all` option: skip every company child (companies, projects, company-knowledge). */
|
|
11
|
+
export declare const SKIP_COMPANY_INDEXES = "--skip-company-indexes";
|
|
3
12
|
/**
|
|
4
13
|
* One-line summaries for targets without their own option grammar. The CLI,
|
|
5
14
|
* the renderer and the bundled shell fallbacks all print the same help text.
|
|
@@ -10,6 +10,54 @@ import { renderSocialDrafts } from "./social-drafts.js";
|
|
|
10
10
|
import { renderThreads } from "./threads.js";
|
|
11
11
|
import { renderWorkers } from "./workers.js";
|
|
12
12
|
const allOrder = ["threads", "orchestrator", "companies", "projects", "company-knowledge", "public-knowledge", "workers", "reports", "social-drafts"];
|
|
13
|
+
const companyChildren = new Set(["companies", "company-knowledge", "projects"]);
|
|
14
|
+
/**
|
|
15
|
+
* `all` accepts `--company <slug>` (repeatable). Without it, `all` keeps the
|
|
16
|
+
* every-company rebuild; the command layer decides whether that is allowed.
|
|
17
|
+
* Slugs are validated before any child runs, so a bad slug writes nothing.
|
|
18
|
+
*/
|
|
19
|
+
/** `all` option: run the companies child without --company (the pre-scoping behavior). */
|
|
20
|
+
export const UNSCOPED_COMPANIES_INDEX = "--unscoped-companies-index";
|
|
21
|
+
/** `all` option: skip every company child (companies, projects, company-knowledge). */
|
|
22
|
+
export const SKIP_COMPANY_INDEXES = "--skip-company-indexes";
|
|
23
|
+
function allCompanyScope(root, args) {
|
|
24
|
+
const companies = [];
|
|
25
|
+
let unscopedCompanies = false;
|
|
26
|
+
let skipCompanyIndexes = false;
|
|
27
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
28
|
+
let value = args[i];
|
|
29
|
+
if (value === UNSCOPED_COMPANIES_INDEX) {
|
|
30
|
+
unscopedCompanies = true;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (value === SKIP_COMPANY_INDEXES) {
|
|
34
|
+
skipCompanyIndexes = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (value === "--company")
|
|
38
|
+
value = args[++i] ?? "";
|
|
39
|
+
else if (value.startsWith("--company="))
|
|
40
|
+
value = value.slice("--company=".length);
|
|
41
|
+
else
|
|
42
|
+
throw new CompanyIndexUserError(`rebuild-index all: unknown option ${value}`);
|
|
43
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value))
|
|
44
|
+
throw new CompanyIndexUserError("rebuild-index all: --company requires a valid company slug");
|
|
45
|
+
let real = false;
|
|
46
|
+
try {
|
|
47
|
+
const stat = fs.lstatSync(path.join(root, "companies", value));
|
|
48
|
+
real = stat.isDirectory() && !stat.isSymbolicLink();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code !== "ENOENT")
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (!real)
|
|
55
|
+
throw new CompanyIndexUserError(`rebuild-index all: no company directory named "${value}"`);
|
|
56
|
+
if (!companies.includes(value))
|
|
57
|
+
companies.push(value);
|
|
58
|
+
}
|
|
59
|
+
return { companies: companies.length > 0 ? companies : undefined, unscopedCompanies, skipCompanyIndexes };
|
|
60
|
+
}
|
|
13
61
|
function companyScope(target, context, args) {
|
|
14
62
|
let company;
|
|
15
63
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -82,15 +130,27 @@ export function renderIndexTarget(target, context, args = []) {
|
|
|
82
130
|
case "threads": return renderThreads(context, args);
|
|
83
131
|
case "workers": return renderWorkers(context);
|
|
84
132
|
case "all": {
|
|
133
|
+
const { companies, unscopedCompanies, skipCompanyIndexes } = allCompanyScope(context.root, args);
|
|
85
134
|
const written = [];
|
|
86
135
|
let errors = 0;
|
|
87
136
|
for (const child of allOrder) {
|
|
88
|
-
|
|
89
|
-
|
|
137
|
+
if (skipCompanyIndexes && companyChildren.has(child)) {
|
|
138
|
+
context.log?.(`rebuild-all-indexes: skip rebuild-${child}-index.sh (no company in this session's scope)`);
|
|
139
|
+
continue;
|
|
90
140
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
141
|
+
// With --company, the per-company children rebuild only those companies.
|
|
142
|
+
const scoped = companies !== undefined && companyChildren.has(child) && !(child === "companies" && unscopedCompanies);
|
|
143
|
+
const runs = scoped
|
|
144
|
+
? companies.map((slug) => (child === "companies" ? ["--company", slug] : [slug]))
|
|
145
|
+
: [[]];
|
|
146
|
+
for (const childArgs of runs) {
|
|
147
|
+
try {
|
|
148
|
+
written.push(...renderIndexTarget(child, context, childArgs).written);
|
|
149
|
+
}
|
|
150
|
+
catch (_error) {
|
|
151
|
+
errors += 1;
|
|
152
|
+
context.log?.(`rebuild-all-indexes: ERROR in rebuild-${child}-index.sh`);
|
|
153
|
+
}
|
|
94
154
|
}
|
|
95
155
|
}
|
|
96
156
|
// The shell sorts and deduplicates the `wrote` paths before JSON output.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session company scope for `hq core rebuild-index`.
|
|
3
|
+
*
|
|
4
|
+
* The renderers in this folder are pure: with no company they rebuild every
|
|
5
|
+
* company. The command layer must not inherit that default inside a
|
|
6
|
+
* company-bound session. A session bound to one or more companies may only
|
|
7
|
+
* rebuild those companies' indexes, so a lane working on one company can never
|
|
8
|
+
* rewrite another company's INDEX.md.
|
|
9
|
+
*
|
|
10
|
+
* The bound set is exactly what `core/scripts/hq-session.sh get company_slugs`
|
|
11
|
+
* prints for the session. The HQ tree's own script is run when it exists,
|
|
12
|
+
* otherwise the copy bundled with this CLI (in-tree implementation). On top of
|
|
13
|
+
* that, any session state that cannot be read or does not add up (unreadable
|
|
14
|
+
* metadata, a primary company that no longer resolves to a real directory, a
|
|
15
|
+
* lock set that names a symlinked company) is indeterminate and refuses the
|
|
16
|
+
* rebuild. It never counts as unbound.
|
|
17
|
+
*
|
|
18
|
+
* An unbound session keeps the every-company rebuild that automation
|
|
19
|
+
* (cleanup --reindex, rebuild-all-indexes.sh) relies on, with a warning.
|
|
20
|
+
*
|
|
21
|
+
* planScopedRebuild() runs before any write. Every refusal it raises is a
|
|
22
|
+
* CompanyIndexUserError raised before the first renderer call.
|
|
23
|
+
*/
|
|
24
|
+
import { type IndexTarget } from "./index.js";
|
|
25
|
+
export interface PlannedRebuild {
|
|
26
|
+
target: IndexTarget;
|
|
27
|
+
args: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface RebuildPlan {
|
|
30
|
+
runs: PlannedRebuild[];
|
|
31
|
+
/** Printed to stderr before the rebuild runs. */
|
|
32
|
+
warning?: string;
|
|
33
|
+
}
|
|
34
|
+
export type SessionCompanyState = {
|
|
35
|
+
kind: "unbound";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "bound";
|
|
38
|
+
companies: string[];
|
|
39
|
+
};
|
|
40
|
+
export declare const ALL_COMPANIES_FLAG = "--all-companies";
|
|
41
|
+
/** Appended to the help of the company targets (projects, company-knowledge, companies). */
|
|
42
|
+
export declare const SESSION_SCOPE_HELP: string;
|
|
43
|
+
export declare const UNBOUND_ALL_COMPANIES_WARNING = "hq: rebuild-index: this session is not bound to a company, so every company's index is rebuilt. Pass --company <slug> to rebuild one.";
|
|
44
|
+
export declare const COMPANY_SCOPE_FLAG_OFF_WARNING = "hq: rebuild-index: cli.rebuild-index-company-scope is off, so the company root indexes (companies/<slug>/INDEX.md) are rebuilt for every company, as before scoping.";
|
|
45
|
+
export declare const PERSONAL_ALL_NOTE = "hq: rebuild-index all: this session is bound to personal; company indexes are skipped and the rest are rebuilt.";
|
|
46
|
+
/**
|
|
47
|
+
* The session's bound company set, as `hq-session.sh get company_slugs`
|
|
48
|
+
* reports it. Throws CompanyIndexUserError when the state is indeterminate.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sessionCompanyState(root: string, env?: NodeJS.ProcessEnv): SessionCompanyState;
|
|
51
|
+
export interface PlanOptions {
|
|
52
|
+
/** cli.rebuild-index-company-scope: whether `companies` may be rebuilt for a single company. */
|
|
53
|
+
companyIndexScopeEnabled: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decide which renderer calls a rebuild may make, before any of them runs.
|
|
57
|
+
*
|
|
58
|
+
* - Bound session: no company means exactly the bound companies; a company
|
|
59
|
+
* outside the set, and --all-companies, are refused.
|
|
60
|
+
* - Unbound session: an explicit company is used as given; otherwise every
|
|
61
|
+
* company is rebuilt, with a warning unless --all-companies was passed.
|
|
62
|
+
* - Indeterminate session state: refused.
|
|
63
|
+
* - `companies` for one company needs cli.rebuild-index-company-scope, whether
|
|
64
|
+
* the company came from the caller or from the bound set. Under `all` the
|
|
65
|
+
* companies child is skipped instead when the flag is off.
|
|
66
|
+
* - Every company is checked to be a real directory before anything runs.
|
|
67
|
+
*/
|
|
68
|
+
export declare function planScopedRebuild(target: IndexTarget, args: readonly string[], root: string, env: NodeJS.ProcessEnv, options: PlanOptions): RebuildPlan;
|
|
69
|
+
//# sourceMappingURL=session-scope.d.ts.map
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session company scope for `hq core rebuild-index`.
|
|
3
|
+
*
|
|
4
|
+
* The renderers in this folder are pure: with no company they rebuild every
|
|
5
|
+
* company. The command layer must not inherit that default inside a
|
|
6
|
+
* company-bound session. A session bound to one or more companies may only
|
|
7
|
+
* rebuild those companies' indexes, so a lane working on one company can never
|
|
8
|
+
* rewrite another company's INDEX.md.
|
|
9
|
+
*
|
|
10
|
+
* The bound set is exactly what `core/scripts/hq-session.sh get company_slugs`
|
|
11
|
+
* prints for the session. The HQ tree's own script is run when it exists,
|
|
12
|
+
* otherwise the copy bundled with this CLI (in-tree implementation). On top of
|
|
13
|
+
* that, any session state that cannot be read or does not add up (unreadable
|
|
14
|
+
* metadata, a primary company that no longer resolves to a real directory, a
|
|
15
|
+
* lock set that names a symlinked company) is indeterminate and refuses the
|
|
16
|
+
* rebuild. It never counts as unbound.
|
|
17
|
+
*
|
|
18
|
+
* An unbound session keeps the every-company rebuild that automation
|
|
19
|
+
* (cleanup --reindex, rebuild-all-indexes.sh) relies on, with a warning.
|
|
20
|
+
*
|
|
21
|
+
* planScopedRebuild() runs before any write. Every refusal it raises is a
|
|
22
|
+
* CompanyIndexUserError raised before the first renderer call.
|
|
23
|
+
*/
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as path from "node:path";
|
|
27
|
+
import { sessionIdFromEnv, validSessionId } from "../core-utils/session-id.js";
|
|
28
|
+
import { resolveBundledAsset } from "../../utils/hq-roots-core.js";
|
|
29
|
+
import { CompanyIndexUserError } from "./companies.js";
|
|
30
|
+
import { SKIP_COMPANY_INDEXES, UNSCOPED_COMPANIES_INDEX } from "./index.js";
|
|
31
|
+
const COMPANY_TARGETS = new Set(["all", "companies", "company-knowledge", "projects"]);
|
|
32
|
+
export const ALL_COMPANIES_FLAG = "--all-companies";
|
|
33
|
+
/** Appended to the help of the company targets (projects, company-knowledge, companies). */
|
|
34
|
+
export const SESSION_SCOPE_HELP = "\nCompany scope: in a company-bound session, a run with no company rebuilds only the bound companies\n" +
|
|
35
|
+
"(core/scripts/hq-session.sh get company_slugs), and a company outside that set is refused.\n" +
|
|
36
|
+
`An unbound session keeps the every-company rebuild (or pass --company <slug>); ${ALL_COMPANIES_FLAG} states that explicitly.\n`;
|
|
37
|
+
export const UNBOUND_ALL_COMPANIES_WARNING = "hq: rebuild-index: this session is not bound to a company, so every company's index is rebuilt. Pass --company <slug> to rebuild one.";
|
|
38
|
+
export const COMPANY_SCOPE_FLAG_OFF_WARNING = "hq: rebuild-index: cli.rebuild-index-company-scope is off, so the company root indexes (companies/<slug>/INDEX.md) are rebuilt for every company, as before scoping.";
|
|
39
|
+
export const PERSONAL_ALL_NOTE = "hq: rebuild-index all: this session is bound to personal; company indexes are skipped and the rest are rebuilt.";
|
|
40
|
+
/**
|
|
41
|
+
* The caller's session id. "" means no session at all: no id in the
|
|
42
|
+
* environment and no workspace/sessions/.current. A .current that exists but
|
|
43
|
+
* cannot be read or does not hold a valid id is indeterminate, not unbound.
|
|
44
|
+
*/
|
|
45
|
+
function callerSessionId(root, env) {
|
|
46
|
+
const fromEnv = sessionIdFromEnv(env);
|
|
47
|
+
if (fromEnv)
|
|
48
|
+
return fromEnv;
|
|
49
|
+
let current;
|
|
50
|
+
try {
|
|
51
|
+
current = fs.readFileSync(path.join(root, "workspace", "sessions", ".current"), "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === "ENOENT")
|
|
55
|
+
return "";
|
|
56
|
+
indeterminate("workspace/sessions/.current is unreadable");
|
|
57
|
+
}
|
|
58
|
+
const id = current.replace(/[\t\n\v\f\r ]/g, "");
|
|
59
|
+
if (!validSessionId(id))
|
|
60
|
+
indeterminate("workspace/sessions/.current does not hold a valid session id");
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
function indeterminate(detail) {
|
|
64
|
+
throw new CompanyIndexUserError(`rebuild-index: cannot determine this session's bound companies (${detail}). Nothing was written.`);
|
|
65
|
+
}
|
|
66
|
+
function realCompanyDirectory(root, slug) {
|
|
67
|
+
try {
|
|
68
|
+
const stat = fs.lstatSync(path.join(root, "companies", slug));
|
|
69
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function metaPrimary(contents) {
|
|
76
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
77
|
+
const m = /^\s*company_slug:[\t ]*(.*)$/.exec(line);
|
|
78
|
+
if (m)
|
|
79
|
+
return m[1].replace(/^"|"$/g, "").trim();
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
/** The hq-session.sh to ask: the HQ tree's own, else the bundled in-tree copy. */
|
|
84
|
+
function sessionScript(root) {
|
|
85
|
+
const own = path.join(root, "core", "scripts", "hq-session.sh");
|
|
86
|
+
try {
|
|
87
|
+
if (fs.statSync(own).isFile())
|
|
88
|
+
return { script: own, bundled: false };
|
|
89
|
+
}
|
|
90
|
+
catch { /* fall through to the bundled copy */ }
|
|
91
|
+
return { script: resolveBundledAsset("core/scripts/hq-session.sh"), bundled: true };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The session's bound company set, as `hq-session.sh get company_slugs`
|
|
95
|
+
* reports it. Throws CompanyIndexUserError when the state is indeterminate.
|
|
96
|
+
*/
|
|
97
|
+
export function sessionCompanyState(root, env = process.env) {
|
|
98
|
+
const sessionId = callerSessionId(root, env);
|
|
99
|
+
if (!sessionId)
|
|
100
|
+
return { kind: "unbound" };
|
|
101
|
+
const sessionDir = path.join(root, "workspace", "sessions", sessionId);
|
|
102
|
+
const metaPath = path.join(sessionDir, "meta.yaml");
|
|
103
|
+
const capabilityPath = path.join(sessionDir, "scope-capability.json");
|
|
104
|
+
let meta;
|
|
105
|
+
try {
|
|
106
|
+
const stat = fs.lstatSync(metaPath);
|
|
107
|
+
if (!stat.isFile())
|
|
108
|
+
indeterminate("session metadata is not a regular file");
|
|
109
|
+
meta = fs.readFileSync(metaPath, "utf8");
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof CompanyIndexUserError)
|
|
113
|
+
throw error;
|
|
114
|
+
if (error.code !== "ENOENT")
|
|
115
|
+
indeterminate("session metadata is unreadable");
|
|
116
|
+
}
|
|
117
|
+
const hasCapability = fs.existsSync(capabilityPath);
|
|
118
|
+
if (meta === undefined && !hasCapability)
|
|
119
|
+
return { kind: "unbound" };
|
|
120
|
+
const { script, bundled } = sessionScript(root);
|
|
121
|
+
const result = spawnSync("bash", [script, "--session-id", sessionId, "get", "company_slugs"], {
|
|
122
|
+
cwd: root,
|
|
123
|
+
encoding: "utf8",
|
|
124
|
+
env: { ...env, HQ_ROOT: root, ...(bundled ? { HQ_HQ_SESSION_NO_CLI: "1" } : {}) },
|
|
125
|
+
timeout: 60_000,
|
|
126
|
+
});
|
|
127
|
+
if (result.error || result.status !== 0)
|
|
128
|
+
indeterminate("hq-session.sh get company_slugs failed");
|
|
129
|
+
const slugs = result.stdout.trim() ? result.stdout.trim().split(",").map((s) => s.trim()) : [];
|
|
130
|
+
const primary = meta === undefined ? "" : metaPrimary(meta);
|
|
131
|
+
if (slugs.length === 0) {
|
|
132
|
+
// A recorded company or capability that resolves to nothing is a broken
|
|
133
|
+
// bind (for example a symlinked or removed company directory), not "unbound".
|
|
134
|
+
if (primary || hasCapability)
|
|
135
|
+
indeterminate("the recorded company does not resolve to a real company directory");
|
|
136
|
+
return { kind: "unbound" };
|
|
137
|
+
}
|
|
138
|
+
if (slugs.some((slug) => !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(slug)))
|
|
139
|
+
indeterminate("the lock set is malformed");
|
|
140
|
+
if (slugs.some((slug) => slug !== "personal" && !realCompanyDirectory(root, slug))) {
|
|
141
|
+
indeterminate("a bound company is not a real company directory");
|
|
142
|
+
}
|
|
143
|
+
return { kind: "bound", companies: slugs };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Pull the scope out of the arguments. Positional slugs count as a company for
|
|
147
|
+
* the targets whose grammar accepts them (projects, company-knowledge); for
|
|
148
|
+
* companies and all only --company does.
|
|
149
|
+
*/
|
|
150
|
+
function parseScope(target, args) {
|
|
151
|
+
const explicit = [];
|
|
152
|
+
const rest = [];
|
|
153
|
+
let allCompanies = false;
|
|
154
|
+
const positional = target === "projects" || target === "company-knowledge";
|
|
155
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
156
|
+
const arg = args[i];
|
|
157
|
+
if (arg === ALL_COMPANIES_FLAG) {
|
|
158
|
+
allCompanies = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (arg === "--company") {
|
|
162
|
+
const value = args[i + 1];
|
|
163
|
+
if (value !== undefined && !value.startsWith("-")) {
|
|
164
|
+
explicit.push(value);
|
|
165
|
+
i += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
rest.push(arg);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg.startsWith("--company=")) {
|
|
172
|
+
const value = arg.slice("--company=".length);
|
|
173
|
+
if (value) {
|
|
174
|
+
explicit.push(value);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
rest.push(arg);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (positional && !arg.startsWith("-")) {
|
|
181
|
+
explicit.push(arg);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
rest.push(arg);
|
|
185
|
+
}
|
|
186
|
+
return { explicit, allCompanies, rest };
|
|
187
|
+
}
|
|
188
|
+
function companyArgs(target, slug) {
|
|
189
|
+
return target === "companies" ? ["--company", slug] : [slug];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Decide which renderer calls a rebuild may make, before any of them runs.
|
|
193
|
+
*
|
|
194
|
+
* - Bound session: no company means exactly the bound companies; a company
|
|
195
|
+
* outside the set, and --all-companies, are refused.
|
|
196
|
+
* - Unbound session: an explicit company is used as given; otherwise every
|
|
197
|
+
* company is rebuilt, with a warning unless --all-companies was passed.
|
|
198
|
+
* - Indeterminate session state: refused.
|
|
199
|
+
* - `companies` for one company needs cli.rebuild-index-company-scope, whether
|
|
200
|
+
* the company came from the caller or from the bound set. Under `all` the
|
|
201
|
+
* companies child is skipped instead when the flag is off.
|
|
202
|
+
* - Every company is checked to be a real directory before anything runs.
|
|
203
|
+
*/
|
|
204
|
+
export function planScopedRebuild(target, args, root, env, options) {
|
|
205
|
+
if (!COMPANY_TARGETS.has(target))
|
|
206
|
+
return { runs: [{ target, args: args.filter((arg) => arg !== ALL_COMPANIES_FLAG) }] };
|
|
207
|
+
const { explicit, allCompanies, rest } = parseScope(target, args);
|
|
208
|
+
const label = `rebuild-index ${target}`;
|
|
209
|
+
// Argument errors first, in the renderers' own words, before the session is read.
|
|
210
|
+
const bad = rest[0];
|
|
211
|
+
if (bad !== undefined) {
|
|
212
|
+
const missingSlug = bad === "--company" || bad === "--company=";
|
|
213
|
+
if (target === "projects" || target === "company-knowledge") {
|
|
214
|
+
throw new CompanyIndexUserError(`${label}: expected a company slug; unknown or incomplete option`);
|
|
215
|
+
}
|
|
216
|
+
throw new CompanyIndexUserError(missingSlug ? `${label}: --company requires a company slug` : `${label}: unknown option ${bad}`);
|
|
217
|
+
}
|
|
218
|
+
if (allCompanies && explicit.length > 0) {
|
|
219
|
+
throw new CompanyIndexUserError(`${label}: pass either --company <slug> or ${ALL_COMPANIES_FLAG}, not both. Nothing was written.`);
|
|
220
|
+
}
|
|
221
|
+
if (target !== "all" && explicit.length > 1) {
|
|
222
|
+
throw new CompanyIndexUserError(target === "companies" ? `${label}: --company may be supplied once` : `${label}: supply only one company slug`);
|
|
223
|
+
}
|
|
224
|
+
const state = sessionCompanyState(root, env);
|
|
225
|
+
let companies;
|
|
226
|
+
let warning;
|
|
227
|
+
if (explicit.length > 0) {
|
|
228
|
+
if (state.kind === "bound") {
|
|
229
|
+
const outside = explicit.filter((slug) => !state.companies.includes(slug));
|
|
230
|
+
if (outside.length > 0) {
|
|
231
|
+
throw new CompanyIndexUserError(`${label}: "${outside[0]}" is outside this session's bound companies (${state.companies.join(", ")}). Nothing was written. Add it with core/scripts/hq-session.sh add company <slug> if this work is meant to span companies.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
companies = [...new Set(explicit)];
|
|
235
|
+
}
|
|
236
|
+
else if (state.kind === "bound") {
|
|
237
|
+
if (allCompanies) {
|
|
238
|
+
throw new CompanyIndexUserError(`${label}: ${ALL_COMPANIES_FLAG} is not allowed in a session bound to ${state.companies.join(", ")}. Nothing was written.`);
|
|
239
|
+
}
|
|
240
|
+
companies = state.companies.filter((slug) => slug !== "personal");
|
|
241
|
+
if (companies.length === 0) {
|
|
242
|
+
// A personal session still rebuilds the indexes that belong to no company.
|
|
243
|
+
if (target === "all")
|
|
244
|
+
return { runs: [{ target, args: [SKIP_COMPANY_INDEXES] }], warning: PERSONAL_ALL_NOTE };
|
|
245
|
+
throw new CompanyIndexUserError(`${label}: this session is bound to personal, which has no company index to rebuild. Nothing was written.`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (!allCompanies) {
|
|
249
|
+
warning = UNBOUND_ALL_COMPANIES_WARNING;
|
|
250
|
+
}
|
|
251
|
+
if (companies === undefined)
|
|
252
|
+
return { runs: [{ target, args: [] }], warning };
|
|
253
|
+
// Validate every company before the first write.
|
|
254
|
+
// A single caller-named company in an unbound session is left to the
|
|
255
|
+
// renderer, which reports a bad slug or directory in its own words before it
|
|
256
|
+
// writes. Anything that fans out to several renderer calls is checked here.
|
|
257
|
+
if (companies.length > 1 || target === "all") {
|
|
258
|
+
for (const slug of companies) {
|
|
259
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(slug) || !realCompanyDirectory(root, slug)) {
|
|
260
|
+
throw new CompanyIndexUserError(`${label}: "${slug}" is not a real company directory. Nothing was written.`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// cli.rebuild-index-company-scope is a kill switch (default on). Off, the
|
|
265
|
+
// companies root-index rebuild goes back to its pre-scoping behavior
|
|
266
|
+
// (every company) with a warning, instead of being refused or skipped.
|
|
267
|
+
const gateOff = !options.companyIndexScopeEnabled;
|
|
268
|
+
if (target === "companies" && gateOff && explicit.length === 0) {
|
|
269
|
+
return { runs: [{ target, args: [] }], warning: COMPANY_SCOPE_FLAG_OFF_WARNING };
|
|
270
|
+
}
|
|
271
|
+
if (target === "all") {
|
|
272
|
+
return {
|
|
273
|
+
runs: [{ target, args: [...companies.flatMap((slug) => ["--company", slug]), ...(gateOff ? [UNSCOPED_COMPANIES_INDEX] : [])] }],
|
|
274
|
+
warning: gateOff ? COMPANY_SCOPE_FLAG_OFF_WARNING : warning,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return { runs: companies.map((slug) => ({ target, args: companyArgs(target, slug) })), warning };
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=session-scope.js.map
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { accountMaxRunning } from "./config.js";
|
|
4
4
|
import { LanesError } from "./errors.js";
|
|
5
|
+
import { liveOwnerMaxMs, lockFileContents, reclaimStaleLock } from "./stale-lock.js";
|
|
5
6
|
import { localHostIdsReferToSameMachine } from "./local-host-identity.js";
|
|
6
7
|
import { quotaBlockFor } from "./quota.js";
|
|
7
8
|
import { isSubagentLane, isTerminalLaneState } from "./types.js";
|
|
@@ -32,7 +33,7 @@ async function withAdmissionLock(lockPath, fn, sleep) {
|
|
|
32
33
|
fd = fs.openSync(lockPath, "wx");
|
|
33
34
|
const ownedStat = fs.fstatSync(fd);
|
|
34
35
|
try {
|
|
35
|
-
fs.writeSync(fd, Buffer.from(
|
|
36
|
+
fs.writeSync(fd, Buffer.from(lockFileContents(), "utf8"));
|
|
36
37
|
}
|
|
37
38
|
catch (err) {
|
|
38
39
|
fs.closeSync(fd);
|
|
@@ -70,26 +71,12 @@ async function withAdmissionLock(lockPath, fn, sleep) {
|
|
|
70
71
|
throw new LanesError("store_lock_timeout", `Timed out waiting for admission lock ${lockPath}`, { path: lockPath });
|
|
71
72
|
}
|
|
72
73
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
ownerPid > 0 &&
|
|
79
|
-
Number.isFinite(timestamp) &&
|
|
80
|
-
Date.now() - timestamp > ADMISSION_LOCK_STALE_MS &&
|
|
81
|
-
!admissionLockOwnerAlive(ownerPid)) {
|
|
82
|
-
if (fs.readFileSync(lockPath, "utf8") !== raw)
|
|
83
|
-
continue;
|
|
84
|
-
try {
|
|
85
|
-
fs.unlinkSync(lockPath);
|
|
86
|
-
}
|
|
87
|
-
catch (unlinkErr) {
|
|
88
|
-
if (unlinkErr.code !== "ENOENT")
|
|
89
|
-
throw unlinkErr;
|
|
90
|
-
}
|
|
74
|
+
if (reclaimStaleLock(lockPath, {
|
|
75
|
+
staleMs: ADMISSION_LOCK_STALE_MS,
|
|
76
|
+
liveOwnerMaxMs: liveOwnerMaxMs(),
|
|
77
|
+
label: "hq lanes admission",
|
|
78
|
+
}, { isAlive: admissionLockOwnerAlive }))
|
|
91
79
|
continue;
|
|
92
|
-
}
|
|
93
80
|
}
|
|
94
81
|
catch (probeErr) {
|
|
95
82
|
if (probeErr.code !== "ENOENT") {
|
|
@@ -44,6 +44,7 @@ import { activeLaneStories, laneStories, terminalEnvelopeStoryStatus } from "./s
|
|
|
44
44
|
import { applyStorySwitchEnvelope } from "./story-switch.js";
|
|
45
45
|
import { readProjectStoryStates } from "./story-ledger.js";
|
|
46
46
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
47
|
+
import { liveOwnerMaxMs, lockFileContents, reclaimStaleLock } from "./stale-lock.js";
|
|
47
48
|
import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
48
49
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
49
50
|
export const QUESTION_AGE_MS = 60 * 60 * 1000;
|
|
@@ -916,8 +917,10 @@ async function withReconcileLock(hqRoot, fn) {
|
|
|
916
917
|
let fd;
|
|
917
918
|
try {
|
|
918
919
|
fd = fs.openSync(lockPath, "wx");
|
|
920
|
+
let held;
|
|
919
921
|
try {
|
|
920
|
-
fs.writeSync(fd, Buffer.from(
|
|
922
|
+
fs.writeSync(fd, Buffer.from(lockFileContents(), "utf8"));
|
|
923
|
+
held = fs.fstatSync(fd);
|
|
921
924
|
}
|
|
922
925
|
catch (err) {
|
|
923
926
|
fs.closeSync(fd);
|
|
@@ -935,7 +938,10 @@ async function withReconcileLock(hqRoot, fn) {
|
|
|
935
938
|
process.stderr.write(`hq lanes reconcile: lock close failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
936
939
|
}
|
|
937
940
|
try {
|
|
938
|
-
|
|
941
|
+
// Unlink only our own lock; a reclaimer may have replaced it.
|
|
942
|
+
const current = fs.statSync(lockPath);
|
|
943
|
+
if (current.dev === held.dev && current.ino === held.ino)
|
|
944
|
+
fs.unlinkSync(lockPath);
|
|
939
945
|
}
|
|
940
946
|
catch (err) {
|
|
941
947
|
const code = err.code;
|
|
@@ -953,20 +959,12 @@ async function withReconcileLock(hqRoot, fn) {
|
|
|
953
959
|
throw new LanesError("store_lock_timeout", `Timed out waiting for reconcile lock ${lockPath}`, { path: lockPath });
|
|
954
960
|
}
|
|
955
961
|
try {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
fs.unlinkSync(lockPath);
|
|
962
|
-
}
|
|
963
|
-
catch (unlinkErr) {
|
|
964
|
-
const unlinkCode = unlinkErr.code;
|
|
965
|
-
if (unlinkCode !== "ENOENT")
|
|
966
|
-
throw unlinkErr;
|
|
967
|
-
}
|
|
962
|
+
if (reclaimStaleLock(lockPath, {
|
|
963
|
+
staleMs: RECONCILE_LOCK_STALE_MS,
|
|
964
|
+
liveOwnerMaxMs: liveOwnerMaxMs(),
|
|
965
|
+
label: "hq lanes reconcile",
|
|
966
|
+
}))
|
|
968
967
|
continue;
|
|
969
|
-
}
|
|
970
968
|
}
|
|
971
969
|
catch (statErr) {
|
|
972
970
|
const statCode = statErr.code;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Shared stale-lock reclaim for the lanes store. A lock file holds
|
|
4
|
+
* `<pid>\n<epoch-ms>\n<hostname>\n`, written right after an exclusive create.
|
|
5
|
+
*
|
|
6
|
+
* Reclaim rules:
|
|
7
|
+
* - Same-host owner pid that no longer exists: reclaim at once.
|
|
8
|
+
* - Same-host owner pid that is alive, cannot be signalled, or is this process
|
|
9
|
+
* (an async holder in this process): keep it until
|
|
10
|
+
* the live-owner ceiling, which only guards against pid reuse and a hung
|
|
11
|
+
* holder. A live holder is never reclaimed by the ordinary stale window.
|
|
12
|
+
* - Legacy lock with no host line whose pid is alive here: same ceiling.
|
|
13
|
+
* - Owner that cannot be verified here (empty, unparseable, foreign or missing
|
|
14
|
+
* host): reclaim once older than the stale window.
|
|
15
|
+
*
|
|
16
|
+
* Age is the larger of the timestamp age and the file mtime age, so an empty
|
|
17
|
+
* lock or a future timestamp still ages out. Reclaim is atomic: the lock is
|
|
18
|
+
* renamed to a unique name that only one reclaimer can win, and a fresh lock
|
|
19
|
+
* displaced by a lost race is linked back into place.
|
|
20
|
+
*/
|
|
21
|
+
export interface StaleLockIo {
|
|
22
|
+
statSync?: (p: string) => fs.Stats;
|
|
23
|
+
readFileSync?: (p: string, enc: "utf8") => string;
|
|
24
|
+
renameSync?: (from: string, to: string) => void;
|
|
25
|
+
unlinkSync?: (p: string) => void;
|
|
26
|
+
linkSync?: (from: string, to: string) => void;
|
|
27
|
+
hostname?: () => string;
|
|
28
|
+
isAlive?: (pid: number) => boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface StaleLockOptions {
|
|
31
|
+
/** Window after which an unverifiable owner is presumed gone. */
|
|
32
|
+
staleMs: number;
|
|
33
|
+
/** Ceiling after which even a live same-host owner is presumed hung or reused. */
|
|
34
|
+
liveOwnerMaxMs: number;
|
|
35
|
+
/** When false, only a verified dead same-host owner is reclaimed. */
|
|
36
|
+
reclaimByAge?: boolean;
|
|
37
|
+
/** Prefix for stderr notices. */
|
|
38
|
+
label?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const DEFAULT_LIVE_OWNER_MAX_MS: number;
|
|
41
|
+
/** HQ_LANES_LOCK_LIVE_OWNER_MAX_MS overrides the live-owner ceiling. */
|
|
42
|
+
export declare function liveOwnerMaxMs(env?: NodeJS.ProcessEnv): number;
|
|
43
|
+
export declare function lockOwnerPidAlive(pid: number): boolean;
|
|
44
|
+
/** Contents to write into a freshly created lock file. */
|
|
45
|
+
export declare function lockFileContents(hostname?: string): string;
|
|
46
|
+
/** Decide whether the lock content/age describes an abandoned lock. */
|
|
47
|
+
export declare function lockIsStale(raw: string, mtimeMs: number, now: number, hostname: string, isAlive: (pid: number) => boolean, options: StaleLockOptions): boolean;
|
|
48
|
+
/** Remove an abandoned lock atomically. Returns true when this call reclaimed it. */
|
|
49
|
+
export declare function reclaimStaleLock(lockPath: string, options: StaleLockOptions, io?: StaleLockIo): boolean;
|
|
50
|
+
//# sourceMappingURL=stale-lock.d.ts.map
|