@indigoai-us/hq-cli 5.273.0-alpha.0 → 5.274.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 +65 -2
- package/README.md +32 -1
- package/dist/command-catalog.generated.d.ts +83 -536
- package/dist/command-catalog.generated.js +129 -714
- package/dist/command-registration-plan.d.ts +12 -0
- package/dist/command-registration-plan.js +2 -0
- package/dist/commands/cloud.js +40 -0
- package/dist/commands/core-checkpoint.js +3 -1
- package/dist/commands/core-command-catalog.d.ts +32 -0
- package/dist/commands/core-command-catalog.js +58 -0
- package/dist/commands/core-ontology.js +9 -4
- package/dist/commands/core.d.ts +4 -0
- package/dist/commands/core.js +79 -75
- package/dist/commands/daemon.d.ts +0 -3
- package/dist/commands/daemon.js +1 -173
- package/dist/commands/deploy-comments.d.ts +3 -0
- package/dist/commands/deploy-comments.js +159 -0
- package/dist/commands/lanes-checks.d.ts +0 -1
- package/dist/commands/lanes-checks.js +217 -54
- package/dist/commands/lanes-hold.js +40 -2
- package/dist/commands/lanes-message.js +98 -14
- package/dist/commands/lanes-move.d.ts +12 -0
- package/dist/commands/lanes-move.js +342 -0
- package/dist/commands/lanes-resume.js +3 -1
- package/dist/commands/lanes-shared.d.ts +39 -3
- package/dist/commands/lanes-shared.js +59 -0
- package/dist/commands/lanes.d.ts +3 -5
- package/dist/commands/lanes.js +206 -478
- package/dist/commands/report.js +2 -14
- package/dist/commands/sync-mode.d.ts +0 -11
- package/dist/commands/sync-mode.js +0 -30
- package/dist/fast-core.js +1 -1
- package/dist/{commands/scaffold-fast.d.ts → fast-scaffold.d.ts} +1 -1
- package/dist/{commands/scaffold-fast.js → fast-scaffold.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/checkpoint-ontology/apply.d.ts +8 -0
- package/dist/lib/checkpoint-ontology/apply.js +33 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +15 -0
- package/dist/lib/checkpoint-ontology/plan.js +15 -0
- package/dist/lib/daemon/catalog.d.ts +2 -18
- package/dist/lib/daemon/catalog.js +2 -18
- package/dist/lib/daemon/config.d.ts +0 -6
- package/dist/lib/daemon/config.js +2 -15
- package/dist/lib/daemon/requests.d.ts +0 -4
- package/dist/lib/daemon/requests.js +0 -2
- package/dist/lib/daemon/run.js +19 -78
- package/dist/lib/daemon/supervisor.d.ts +0 -8
- package/dist/lib/daemon/supervisor.js +1 -101
- package/dist/lib/deploy-api.d.ts +6 -0
- package/dist/lib/deploy-api.js +21 -0
- package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +7 -0
- package/dist/lib/doctor/checks/scaffold-cli-floor.js +73 -0
- package/dist/lib/doctor/fix/apply.d.ts +17 -2
- package/dist/lib/doctor/fix/apply.js +111 -57
- package/dist/lib/doctor/fix/json-output.d.ts +9 -1
- package/dist/lib/doctor/fix/json-output.js +11 -0
- package/dist/lib/doctor/registry.js +2 -0
- package/dist/lib/first-sync-next-step.d.ts +37 -0
- package/dist/lib/first-sync-next-step.js +189 -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/lanes/hire-grant-dispatch.d.ts +6 -1
- package/dist/lib/lanes/hire-grant-dispatch.js +78 -3
- package/dist/lib/lanes/keep-alive.d.ts +3 -2
- package/dist/lib/lanes/keep-alive.js +2 -2
- package/dist/lib/lanes/merge-command.d.ts +1 -0
- package/dist/lib/lanes/merge-command.js +299 -33
- package/dist/lib/lanes/orphans.d.ts +7 -0
- package/dist/lib/lanes/orphans.js +39 -4
- package/dist/lib/lanes/tracker-store.d.ts +2 -18
- package/dist/lib/lanes/tracker-store.js +1 -172
- package/dist/lib/scaffold-cli-floor.d.ts +9 -0
- package/dist/lib/scaffold-cli-floor.js +50 -0
- package/dist/main.js +4 -1
- package/dist/utils/cli-telemetry.d.ts +7 -1
- package/dist/utils/cli-telemetry.js +84 -8
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/scaffold-fast-import-guard.d.ts +3 -0
- package/dist/utils/scaffold-fast-import-guard.js +28 -0
- package/dist/utils/scaffold-packlist-guard.d.ts +7 -0
- package/dist/utils/scaffold-packlist-guard.js +23 -0
- package/dist/utils/scaffold-sibling-references.d.ts +21 -0
- package/dist/utils/scaffold-sibling-references.js +156 -0
- package/package.json +2 -2
- package/dist/commands/lanes-import.d.ts +0 -14
- package/dist/commands/lanes-import.js +0 -1210
- package/dist/commands/lanes-tracker.d.ts +0 -18
- package/dist/commands/lanes-tracker.js +0 -452
- package/dist/lib/daemon/sync-runner.d.ts +0 -58
- package/dist/lib/daemon/sync-runner.js +0 -277
package/dist/commands/report.js
CHANGED
|
@@ -5,9 +5,9 @@ import * as os from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { ensureCognitoToken, } from "../utils/cognito-session.js";
|
|
7
7
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
8
|
+
import { DEPLOY_API_URL, deployApiRequest, deployHeaders } from "../lib/deploy-api.js";
|
|
8
9
|
import { listReportTemplates } from "../lib/report-templates.js";
|
|
9
10
|
import { adoptReport, buildReportTarball, conventionReportDir, deployOrgContext, ReportPublishError, plainFailureReason, publishReport, } from "../lib/report-publish.js";
|
|
10
|
-
const DEPLOY_API_URL = process.env.HQ_DEPLOY_API_URL ?? "https://api.indigo-hq.com";
|
|
11
11
|
const REPORT_CHANNEL_STATE_FILE = "report-channels.json";
|
|
12
12
|
const MAX_REPORT_DISCOVERY_CHANNELS = 50;
|
|
13
13
|
const JOB_EXECUTION_DISABLED_MESSAGE = "Scheduled jobs are turned off for this company. A company owner can turn them back on.";
|
|
@@ -214,22 +214,10 @@ export async function patchReport(token, report, patch) {
|
|
|
214
214
|
throw new ReportPublishError("Report update response was incomplete.");
|
|
215
215
|
return body.report;
|
|
216
216
|
}
|
|
217
|
-
function deployHeaders(token, json = true, extra = {}) {
|
|
218
|
-
return {
|
|
219
|
-
Authorization: `Bearer ${token}`,
|
|
220
|
-
Accept: "application/json",
|
|
221
|
-
...(json ? { "Content-Type": "application/json" } : {}),
|
|
222
|
-
...extra,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
217
|
async function deployJson(token, route, method, body, extraHeaders = {}) {
|
|
226
218
|
let res;
|
|
227
219
|
try {
|
|
228
|
-
res = await
|
|
229
|
-
method,
|
|
230
|
-
headers: deployHeaders(token, body !== undefined, extraHeaders),
|
|
231
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
232
|
-
});
|
|
220
|
+
res = await deployApiRequest(token, route, method, body, extraHeaders);
|
|
233
221
|
}
|
|
234
222
|
catch {
|
|
235
223
|
throw new ReportPublishError("Could not reach hq-deploy.");
|
|
@@ -70,15 +70,6 @@ export interface SetSyncModeResult {
|
|
|
70
70
|
export interface ShowSyncModesOptions {
|
|
71
71
|
vaultClient: SyncModeVaultClient;
|
|
72
72
|
}
|
|
73
|
-
export interface GetSyncModeOptions {
|
|
74
|
-
companySlug: string;
|
|
75
|
-
vaultClient: SyncModeVaultClient;
|
|
76
|
-
}
|
|
77
|
-
export interface GetSyncModeResult {
|
|
78
|
-
membershipId: string;
|
|
79
|
-
companySlug: string;
|
|
80
|
-
config: MembershipSyncConfig;
|
|
81
|
-
}
|
|
82
73
|
export interface ShowSyncModesRow {
|
|
83
74
|
companySlug: string;
|
|
84
75
|
companyName?: string;
|
|
@@ -109,8 +100,6 @@ export declare function setSyncMode(options: SetSyncModeOptions): Promise<SetSyn
|
|
|
109
100
|
* stable table output.
|
|
110
101
|
*/
|
|
111
102
|
export declare function showSyncModes(options: ShowSyncModesOptions): Promise<ShowSyncModesRow[]>;
|
|
112
|
-
/** Read one company's effective sync mode without hiding API read failures. */
|
|
113
|
-
export declare function getSyncModeForCompany(options: GetSyncModeOptions): Promise<GetSyncModeResult>;
|
|
114
103
|
/**
|
|
115
104
|
* Render the `--show` table as plain text. No external dep — hq-cli doesn't
|
|
116
105
|
* use cli-table3, so we hand-format columns to match the existing chalk +
|
|
@@ -163,36 +163,6 @@ export async function showSyncModes(options) {
|
|
|
163
163
|
rows.sort((a, b) => a.companySlug.localeCompare(b.companySlug));
|
|
164
164
|
return rows;
|
|
165
165
|
}
|
|
166
|
-
/** Read one company's effective sync mode without hiding API read failures. */
|
|
167
|
-
export async function getSyncModeForCompany(options) {
|
|
168
|
-
const { companySlug, vaultClient } = options;
|
|
169
|
-
const memberships = await vaultClient.listMyMemberships();
|
|
170
|
-
const enriched = await Promise.all(memberships.map(async (membership) => {
|
|
171
|
-
try {
|
|
172
|
-
const entity = await vaultClient.entity.get(membership.companyUid);
|
|
173
|
-
return { ok: true, membership, slug: entity.slug };
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
return { ok: false, membership, error };
|
|
177
|
-
}
|
|
178
|
-
}));
|
|
179
|
-
const match = enriched.find((row) => row.ok && row.slug === companySlug);
|
|
180
|
-
if (!match) {
|
|
181
|
-
const failedLookup = enriched.find((row) => !row.ok);
|
|
182
|
-
if (failedLookup && !failedLookup.ok)
|
|
183
|
-
throw failedLookup.error;
|
|
184
|
-
throw new Error(`No membership found for company '${companySlug}'.`);
|
|
185
|
-
}
|
|
186
|
-
for (const failedLookup of enriched) {
|
|
187
|
-
if (!failedLookup.ok) {
|
|
188
|
-
const detail = failedLookup.error instanceof Error ? failedLookup.error.message : String(failedLookup.error);
|
|
189
|
-
console.error(`getSyncModeForCompany: entity lookup failed for membership ${failedLookup.membership.membershipKey} while resolving '${companySlug}': ${detail}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const membershipId = match.membership.membershipKey;
|
|
193
|
-
const config = await vaultClient.getMembershipSyncConfig(membershipId);
|
|
194
|
-
return { membershipId, companySlug, config };
|
|
195
|
-
}
|
|
196
166
|
// ── Table rendering (used by CLI action, separable for tests) ──────────────
|
|
197
167
|
/**
|
|
198
168
|
* Render the `--show` table as plain text. No external dep — hq-cli doesn't
|
package/dist/fast-core.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { resolveLiveRoot } from "./utils/hq-roots.js";
|
|
11
11
|
import { runBundledScript } from "./utils/run-bundled-script.js";
|
|
12
|
-
import { parseFastCore } from "./
|
|
12
|
+
import { parseFastCore } from "./fast-scaffold.js";
|
|
13
13
|
/**
|
|
14
14
|
* Run the fast-path scaffold script named in `argv` and exit with its code.
|
|
15
15
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
|
@@ -38,4 +38,4 @@ export type ParsedFastCore = {
|
|
|
38
38
|
export declare function parseFastCore(argv: readonly string[]): ParsedFastCore | null;
|
|
39
39
|
/** Whether this invocation is a fast-path scaffold dispatch. */
|
|
40
40
|
export declare function isFastCoreRequest(argv: readonly string[]): boolean;
|
|
41
|
-
//# sourceMappingURL=scaffold
|
|
41
|
+
//# sourceMappingURL=fast-scaffold.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "./node-preflight.js";
|
|
3
3
|
import "./node-network-compat.js";
|
|
4
4
|
import { isVersionRequest } from "./version-request.js";
|
|
5
|
-
import { isFastCoreRequest } from "./
|
|
5
|
+
import { isFastCoreRequest } from "./fast-scaffold.js";
|
|
6
6
|
export declare const __test__: {
|
|
7
7
|
isVersionRequest: typeof isVersionRequest;
|
|
8
8
|
isFastCoreRequest: typeof isFastCoreRequest;
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import "./node-network-compat.js";
|
|
|
6
6
|
import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
|
|
7
7
|
// Dependency-light: a pure parser + table, no command graph. Safe to load on
|
|
8
8
|
// every path (including --version) without reintroducing the heavy startup.
|
|
9
|
-
import { isFastCoreRequest } from "./
|
|
9
|
+
import { isFastCoreRequest } from "./fast-scaffold.js";
|
|
10
10
|
// Also dependency-light: registers a process-level rejection boundary; its only
|
|
11
11
|
// heavy import (main.js) is lazy and reached only when a rejection fires.
|
|
12
12
|
import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.js";
|
|
@@ -33,7 +33,7 @@ if (isVersionRequest(process.argv)) {
|
|
|
33
33
|
else if (isFastCoreRequest(process.argv)) {
|
|
34
34
|
// Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
|
|
35
35
|
// dispatches straight to the bundled-script runner, so it never pays for the
|
|
36
|
-
// ~60-module command graph main.ts imports. See src/
|
|
36
|
+
// ~60-module command graph main.ts imports. See src/fast-scaffold.ts.
|
|
37
37
|
void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv));
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
@@ -105,6 +105,14 @@ export declare class ApplyError extends Error {
|
|
|
105
105
|
}
|
|
106
106
|
/** Validate one raw candidate. Returns the candidate or a rejection reason. */
|
|
107
107
|
export declare function parseCandidate(raw: unknown): Candidate | string;
|
|
108
|
+
/**
|
|
109
|
+
* checkpoint.ontology.named-evidence: true when `evidence` holds one of
|
|
110
|
+
* `names` as a word, matching case. A name inside a path, domain, handle, tag
|
|
111
|
+
* or identifier does not count: `indigoai-us/hq`, `getindigo.ai`,
|
|
112
|
+
* `C:\\Indigo\\hq`, `scope: company:indigo`, `Indigo:internal`, `@indigo`,
|
|
113
|
+
* `` `indigo` ``.
|
|
114
|
+
*/
|
|
115
|
+
export declare function evidenceNames(evidence: string, names: string[]): boolean;
|
|
108
116
|
type Resolution = {
|
|
109
117
|
tier: 1;
|
|
110
118
|
target: string;
|
|
@@ -63,6 +63,25 @@ export function parseCandidate(raw) {
|
|
|
63
63
|
evidence: typeof c.evidence === "string" ? c.evidence : "",
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* checkpoint.ontology.named-evidence: true when `evidence` holds one of
|
|
68
|
+
* `names` as a word, matching case. A name inside a path, domain, handle, tag
|
|
69
|
+
* or identifier does not count: `indigoai-us/hq`, `getindigo.ai`,
|
|
70
|
+
* `C:\\Indigo\\hq`, `scope: company:indigo`, `Indigo:internal`, `@indigo`,
|
|
71
|
+
* `` `indigo` ``.
|
|
72
|
+
*/
|
|
73
|
+
export function evidenceNames(evidence, names) {
|
|
74
|
+
for (const raw of names) {
|
|
75
|
+
const name = raw.trim();
|
|
76
|
+
if (!name || !evidence)
|
|
77
|
+
continue;
|
|
78
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
79
|
+
const word = new RegExp(`(?<![\\p{L}\\p{N}_\\-./\\\\@:\`#])${escaped}(?![\\p{L}\\p{N}_\\-/\\\\@\`]|[.:#][\\p{L}\\p{N}])`, "u");
|
|
80
|
+
if (word.test(evidence))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
66
85
|
/** Design section 5.3, tiers 1 to 4. */
|
|
67
86
|
export function resolveCandidate(index, type, canonical) {
|
|
68
87
|
const normalized = normalizeEntityName(canonical, type);
|
|
@@ -281,6 +300,9 @@ export function applyOntologyRun(options) {
|
|
|
281
300
|
if (types.size > 1)
|
|
282
301
|
crossTypeNames.add(name);
|
|
283
302
|
}
|
|
303
|
+
// checkpoint.ontology.named-evidence: the company's own company entity.
|
|
304
|
+
const ownCompanyRel = `ontology/entities/company/${company.slug}.md`;
|
|
305
|
+
const isOwnCompany = (entity, rel) => entity.type === "company" && (rel === ownCompanyRel || crossTypeKey(entity.canonical_name) === crossTypeKey(company.slug));
|
|
284
306
|
const inputs = liveInputs(options.hqRoot, company, plan);
|
|
285
307
|
const blocked = new Set();
|
|
286
308
|
const updates = new Map();
|
|
@@ -339,6 +361,13 @@ export function applyOntologyRun(options) {
|
|
|
339
361
|
note(report.rejected, "ambiguous_type");
|
|
340
362
|
continue;
|
|
341
363
|
}
|
|
364
|
+
if (plan.named_evidence === true &&
|
|
365
|
+
input.kind === "knowledge" &&
|
|
366
|
+
isOwnCompany(linked, resolved.target) &&
|
|
367
|
+
!evidenceNames(candidate.evidence, [candidate.name, ...candidate.aliases])) {
|
|
368
|
+
note(report.rejected, "unnamed_in_evidence");
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
342
371
|
report.matched += 1;
|
|
343
372
|
report.links.push({ input: candidate.input, action: "link", type: linked.type, canonical_name: linked.canonical_name, path: resolved.target });
|
|
344
373
|
if (resolved.tier === 1 && resolved.duplicates.length > 0) {
|
|
@@ -379,6 +408,10 @@ export function applyOntologyRun(options) {
|
|
|
379
408
|
? [...(index.canonical.get(entityKey("project", normalizeEntityName(canonical, "project"))) ?? [])]
|
|
380
409
|
: [];
|
|
381
410
|
if (projects.length === 1) {
|
|
411
|
+
if (plan.named_evidence === true && !evidenceNames(candidate.evidence, [candidate.name, ...candidate.aliases])) {
|
|
412
|
+
note(report.rejected, "unnamed_in_evidence");
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
382
415
|
const linked = index.byRel.get(projects[0]);
|
|
383
416
|
report.matched += 1;
|
|
384
417
|
report.links.push({ input: candidate.input, action: "link", type: linked.type, canonical_name: linked.canonical_name, path: projects[0] });
|
|
@@ -81,6 +81,19 @@ export declare const SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG = "checkpoint.ontology.sk
|
|
|
81
81
|
* to 0.709 at precision 0.747 to 0.731 (61 of the 63 added links are HQ).
|
|
82
82
|
*/
|
|
83
83
|
export declare const CONCEPT_PROJECT_LINK_FLAG = "checkpoint.ontology.concept-project-link";
|
|
84
|
+
/**
|
|
85
|
+
* A knowledge link to the company's own company entity, or a
|
|
86
|
+
* concept-project-link redirect, needs evidence that names the entity as a
|
|
87
|
+
* word: the candidate name or one of its aliases, not inside a path, domain,
|
|
88
|
+
* handle, tag or identifier (`indigoai-us/hq`, `getindigo.ai`,
|
|
89
|
+
* `scope: company:indigo`). The plan carries `named_evidence: true`, and apply
|
|
90
|
+
* rejects such a link as `unnamed_in_evidence`. CO-57: on the six CO-52 runs,
|
|
91
|
+
* 67 of 143 false positives were links to company/indigo and project/HQ, most
|
|
92
|
+
* on evidence that only held the name inside an identifier; a replay of this
|
|
93
|
+
* rule raised precision on the concept-project-link arm from 0.705 to 0.750
|
|
94
|
+
* and lost no gold link.
|
|
95
|
+
*/
|
|
96
|
+
export declare const NAMED_EVIDENCE_FLAG = "checkpoint.ontology.named-evidence";
|
|
84
97
|
export declare const PLAN_FILE = "ontology-plan.json";
|
|
85
98
|
export declare const CANDIDATES_FILE = "ontology-candidates.json";
|
|
86
99
|
export declare const REPORT_FILE = "ontology-report.json";
|
|
@@ -153,6 +166,8 @@ export interface OntologyPlan {
|
|
|
153
166
|
skip_ambiguous_concept_links?: true;
|
|
154
167
|
/** Set by checkpoint.ontology.concept-project-link: apply links a knowledge concept with an ambiguous alias to the one project of that canonical name. */
|
|
155
168
|
concept_project_link?: true;
|
|
169
|
+
/** Set by checkpoint.ontology.named-evidence: apply rejects a knowledge link to the company's own entity, or a concept-project-link redirect, whose evidence does not name it as a word. */
|
|
170
|
+
named_evidence?: true;
|
|
156
171
|
inputs: PlanInput[];
|
|
157
172
|
/** The session's summary, decisions and learnings, for the `session` input. */
|
|
158
173
|
session_text: string | null;
|
|
@@ -85,6 +85,19 @@ export const SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG = "checkpoint.ontology.skip-ambig
|
|
|
85
85
|
* to 0.709 at precision 0.747 to 0.731 (61 of the 63 added links are HQ).
|
|
86
86
|
*/
|
|
87
87
|
export const CONCEPT_PROJECT_LINK_FLAG = "checkpoint.ontology.concept-project-link";
|
|
88
|
+
/**
|
|
89
|
+
* A knowledge link to the company's own company entity, or a
|
|
90
|
+
* concept-project-link redirect, needs evidence that names the entity as a
|
|
91
|
+
* word: the candidate name or one of its aliases, not inside a path, domain,
|
|
92
|
+
* handle, tag or identifier (`indigoai-us/hq`, `getindigo.ai`,
|
|
93
|
+
* `scope: company:indigo`). The plan carries `named_evidence: true`, and apply
|
|
94
|
+
* rejects such a link as `unnamed_in_evidence`. CO-57: on the six CO-52 runs,
|
|
95
|
+
* 67 of 143 false positives were links to company/indigo and project/HQ, most
|
|
96
|
+
* on evidence that only held the name inside an identifier; a replay of this
|
|
97
|
+
* rule raised precision on the concept-project-link arm from 0.705 to 0.750
|
|
98
|
+
* and lost no gold link.
|
|
99
|
+
*/
|
|
100
|
+
export const NAMED_EVIDENCE_FLAG = "checkpoint.ontology.named-evidence";
|
|
88
101
|
export const PLAN_FILE = "ontology-plan.json";
|
|
89
102
|
export const CANDIDATES_FILE = "ontology-candidates.json";
|
|
90
103
|
export const REPORT_FILE = "ontology-report.json";
|
|
@@ -264,6 +277,7 @@ export async function planOntologyRun(options) {
|
|
|
264
277
|
const firstNamePeople = await options.readFlag(FIRST_NAME_PEOPLE_FLAG, company);
|
|
265
278
|
const skipAmbiguousConceptLinks = await options.readFlag(SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG, company);
|
|
266
279
|
const conceptProjectLink = await options.readFlag(CONCEPT_PROJECT_LINK_FLAG, company);
|
|
280
|
+
const namedEvidence = await options.readFlag(NAMED_EVIDENCE_FLAG, company);
|
|
267
281
|
const touched = sessionTouched(hqRoot, company, payload.files);
|
|
268
282
|
// A first run takes its boundary from the entity tree, so the ledger is
|
|
269
283
|
// refreshed before merge-back moves any `last_updated` forward.
|
|
@@ -366,6 +380,7 @@ export async function planOntologyRun(options) {
|
|
|
366
380
|
...(firstNamePeople ? { first_name_people: true } : {}),
|
|
367
381
|
...(skipAmbiguousConceptLinks ? { skip_ambiguous_concept_links: true } : {}),
|
|
368
382
|
...(conceptProjectLink ? { concept_project_link: true } : {}),
|
|
383
|
+
...(namedEvidence ? { named_evidence: true } : {}),
|
|
369
384
|
inputs,
|
|
370
385
|
session_text: sessionText,
|
|
371
386
|
pending_after_run: remaining,
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
* them instead of silently dropping them.
|
|
11
11
|
*/
|
|
12
12
|
import type { MachineType } from "./machine-type.js";
|
|
13
|
-
import type { SyncOwner } from "./config.js";
|
|
14
|
-
import { type SyncRunnerProcess } from "./sync-runner.js";
|
|
15
13
|
export declare const SERVICE_NAMES: readonly ["sync", "mesh", "heartbeat", "inbox", "bots", "remote-control", "codex-remote-control", "session-host", "lanes-reconcile", "search-index", "jobs-reconcile", "updater", "housekeeping"];
|
|
16
14
|
export type ServiceName = (typeof SERVICE_NAMES)[number];
|
|
17
15
|
export declare function isServiceName(value: unknown): value is ServiceName;
|
|
@@ -53,8 +51,6 @@ export interface UnitPlan {
|
|
|
53
51
|
restart: "always" | "on-failure";
|
|
54
52
|
intervalSec?: number;
|
|
55
53
|
initialDelaySec?: number;
|
|
56
|
-
/** Kill and restart this child after silence on its liveness stream. */
|
|
57
|
-
heartbeatTimeoutMs?: number;
|
|
58
54
|
command: UnitCommand | null;
|
|
59
55
|
env: Record<string, string>;
|
|
60
56
|
blocked?: UnitBlock;
|
|
@@ -72,23 +68,11 @@ export interface PlanContext {
|
|
|
72
68
|
sessionHostRunnerPath: string | null;
|
|
73
69
|
/** Environment retained from legacy outpost sync systemd drop-ins. */
|
|
74
70
|
legacySyncEnv: Record<string, string>;
|
|
75
|
-
/** Keep the local sync runner stopped until the operator resumes it. */
|
|
76
|
-
syncPaused: boolean;
|
|
77
|
-
/** Persisted/effective owner selected for this machine. */
|
|
78
|
-
syncOwner: SyncOwner;
|
|
79
|
-
/** A legacy desktop app or sync unit is still managing sync. */
|
|
80
|
-
desktopAppManagesSync: boolean;
|
|
81
|
-
/** Full process list, or null when the daemon could not read it. */
|
|
82
|
-
syncRunnerProcesses: readonly SyncRunnerProcess[] | null;
|
|
83
|
-
/** PID recorded in the watch-owner status, when it is readable. */
|
|
84
|
-
syncRunnerOwnerPid: number | null;
|
|
85
|
-
/** Sync runner children already started by this daemon before their lease is written. */
|
|
86
|
-
daemonSyncRunnerPids?: ReadonlySet<number>;
|
|
87
|
-
/** The protocol-event heartbeat watchdog timeout for the sync runner. */
|
|
88
|
-
syncHeartbeatTimeoutMs: number;
|
|
89
71
|
signedIn: boolean;
|
|
90
72
|
/** ~/.hq-agent/kit.json exists. */
|
|
91
73
|
kitConfigured: boolean;
|
|
74
|
+
/** The HQ desktop app runs its own sync on this machine. */
|
|
75
|
+
desktopAppManagesSync: boolean;
|
|
92
76
|
bots: readonly string[];
|
|
93
77
|
/** Name of a pre-daemon unit that still runs this unit id, or null. */
|
|
94
78
|
legacyOwner: (unitId: string) => string | null;
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* with no entrypoint yet stay in the catalog as `unavailable` so status shows
|
|
10
10
|
* them instead of silently dropping them.
|
|
11
11
|
*/
|
|
12
|
-
import { SYNC_RUNNER_OWNER, syncRunnerUnleasedBlockReason } from "./sync-runner.js";
|
|
13
12
|
export const SERVICE_NAMES = [
|
|
14
13
|
"sync",
|
|
15
14
|
"mesh",
|
|
@@ -128,29 +127,14 @@ function planService(service, ctx) {
|
|
|
128
127
|
command: {
|
|
129
128
|
type: "node",
|
|
130
129
|
script: ctx.syncRunnerPath,
|
|
131
|
-
args: ["--companies", "--direction", "both", "--on-conflict", "keep", "--hq-root", ctx.hqRoot ?? "", "--watch", "--event-push"
|
|
130
|
+
args: ["--companies", "--direction", "both", "--on-conflict", "keep", "--hq-root", ctx.hqRoot ?? "", "--watch", "--event-push"],
|
|
132
131
|
},
|
|
133
132
|
env: { ...ctx.legacySyncEnv },
|
|
134
|
-
heartbeatTimeoutMs: ctx.syncHeartbeatTimeoutMs,
|
|
135
133
|
});
|
|
136
|
-
let ownerBlock;
|
|
137
|
-
if (ctx.machineType === "desktop" && ctx.syncOwner === "desktop-app" && ctx.desktopAppManagesSync) {
|
|
138
|
-
ownerBlock = "the desktop app is still managing sync (run `hq daemon sync take-ownership` after it stops its runner)";
|
|
139
|
-
}
|
|
140
|
-
else if (ctx.machineType === "desktop" &&
|
|
141
|
-
ctx.syncOwner === "daemon" &&
|
|
142
|
-
!ctx.syncPaused &&
|
|
143
|
-
ctx.hqRoot &&
|
|
144
|
-
ctx.signedIn) {
|
|
145
|
-
ownerBlock = ctx.syncRunnerProcesses === null
|
|
146
|
-
? "could not check for an existing sync runner; waiting to avoid starting a duplicate"
|
|
147
|
-
: syncRunnerUnleasedBlockReason(ctx.hqRoot, ctx.syncRunnerProcesses, ctx.syncRunnerOwnerPid, ctx.platform, ctx.daemonSyncRunnerPids);
|
|
148
|
-
}
|
|
149
134
|
unit.blocked = firstBlock([
|
|
150
|
-
[ctx.
|
|
135
|
+
[ctx.desktopAppManagesSync, "the HQ desktop app is handling sync on this machine"],
|
|
151
136
|
[!ctx.hqRoot, NO_HQ_ROOT],
|
|
152
137
|
[!ctx.signedIn, NOT_SIGNED_IN],
|
|
153
|
-
[!!ownerBlock, ownerBlock ?? ""],
|
|
154
138
|
]);
|
|
155
139
|
return [unit];
|
|
156
140
|
}
|
|
@@ -12,17 +12,11 @@ export interface DaemonConfig {
|
|
|
12
12
|
hqRoot?: string;
|
|
13
13
|
/** Exact hq-cloud package version preferred by this daemon, including prereleases. */
|
|
14
14
|
hqCloudVersion?: string;
|
|
15
|
-
/** Persisted machine-local sync pause. */
|
|
16
|
-
syncPaused?: boolean;
|
|
17
|
-
/** Explicit desktop handoff; absent values preserve the legacy per-machine owner. */
|
|
18
|
-
syncOwner?: SyncOwner;
|
|
19
15
|
/** Services turned on here beyond the machine type's defaults. */
|
|
20
16
|
enable: string[];
|
|
21
17
|
/** Default services turned off here. */
|
|
22
18
|
disable: string[];
|
|
23
19
|
}
|
|
24
|
-
export type SyncOwner = "daemon" | "desktop-app";
|
|
25
|
-
export declare function effectiveSyncOwner(config: Pick<DaemonConfig, "syncOwner">, machineType: MachineType): SyncOwner;
|
|
26
20
|
export declare function emptyDaemonConfig(): DaemonConfig;
|
|
27
21
|
export declare function readDaemonConfig(file: string): DaemonConfig;
|
|
28
22
|
export declare function writeDaemonConfig(file: string, config: DaemonConfig): void;
|
|
@@ -10,11 +10,8 @@ import { valid as validSemver } from "semver";
|
|
|
10
10
|
import { isServiceName } from "./catalog.js";
|
|
11
11
|
import { isMachineType } from "./machine-type.js";
|
|
12
12
|
import { ensurePrivateDir } from "./paths.js";
|
|
13
|
-
export function effectiveSyncOwner(config, machineType) {
|
|
14
|
-
return config.syncOwner ?? (machineType === "desktop" ? "desktop-app" : "daemon");
|
|
15
|
-
}
|
|
16
13
|
export function emptyDaemonConfig() {
|
|
17
|
-
return { version: 1,
|
|
14
|
+
return { version: 1, enable: [], disable: [] };
|
|
18
15
|
}
|
|
19
16
|
function stringList(value) {
|
|
20
17
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
@@ -39,16 +36,9 @@ export function readDaemonConfig(file) {
|
|
|
39
36
|
if (!raw || typeof raw !== "object")
|
|
40
37
|
throw new Error(`hq daemon config at ${file} is not a JSON object`);
|
|
41
38
|
const obj = raw;
|
|
42
|
-
const config = {
|
|
43
|
-
version: 1,
|
|
44
|
-
syncPaused: obj.syncPaused === true,
|
|
45
|
-
enable: stringList(obj.enable),
|
|
46
|
-
disable: stringList(obj.disable),
|
|
47
|
-
};
|
|
39
|
+
const config = { version: 1, enable: stringList(obj.enable), disable: stringList(obj.disable) };
|
|
48
40
|
if (isMachineType(obj.machineType))
|
|
49
41
|
config.machineType = obj.machineType;
|
|
50
|
-
if (obj.syncOwner === "daemon" || obj.syncOwner === "desktop-app")
|
|
51
|
-
config.syncOwner = obj.syncOwner;
|
|
52
42
|
if (typeof obj.hqRoot === "string" && obj.hqRoot.trim())
|
|
53
43
|
config.hqRoot = obj.hqRoot;
|
|
54
44
|
if (typeof obj.hqCloudVersion === "string" && validSemver(obj.hqCloudVersion) === obj.hqCloudVersion) {
|
|
@@ -66,9 +56,6 @@ export function writeDaemonConfig(file, config) {
|
|
|
66
56
|
ordered.hqRoot = config.hqRoot;
|
|
67
57
|
if (config.hqCloudVersion)
|
|
68
58
|
ordered.hqCloudVersion = config.hqCloudVersion;
|
|
69
|
-
ordered.syncPaused = config.syncPaused === true;
|
|
70
|
-
if (config.syncOwner)
|
|
71
|
-
ordered.syncOwner = config.syncOwner;
|
|
72
59
|
ordered.enable = config.enable;
|
|
73
60
|
ordered.disable = config.disable;
|
|
74
61
|
fs.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}\n`, { mode: 0o600 });
|
|
@@ -15,10 +15,6 @@ export type ControlRequest = {
|
|
|
15
15
|
action: "stop";
|
|
16
16
|
unit: string;
|
|
17
17
|
}
|
|
18
|
-
/** Re-read persisted configuration and apply any changed blocks immediately. */
|
|
19
|
-
| {
|
|
20
|
-
action: "reconcile";
|
|
21
|
-
}
|
|
22
18
|
/** Restart the whole daemon, for example after the updater installed a new CLI. */
|
|
23
19
|
| {
|
|
24
20
|
action: "restart-daemon";
|
|
@@ -19,8 +19,6 @@ function isControlRequest(value) {
|
|
|
19
19
|
return typeof v.unit === "string";
|
|
20
20
|
if (v.action === "restart-daemon")
|
|
21
21
|
return true;
|
|
22
|
-
if (v.action === "reconcile")
|
|
23
|
-
return true;
|
|
24
22
|
return false;
|
|
25
23
|
}
|
|
26
24
|
// Orders requests written by one process within the same millisecond; the
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -16,7 +16,7 @@ import { agentKitPaths } from "../agent-kit/paths.js";
|
|
|
16
16
|
import { readBotConfig } from "../bot/config.js";
|
|
17
17
|
import { botsRoot } from "../bot/paths.js";
|
|
18
18
|
import { CLAUDE_RUNNER_SCRIPT, CODEX_RUNNER_SCRIPT, enabledServices, planUnits } from "./catalog.js";
|
|
19
|
-
import {
|
|
19
|
+
import { readDaemonConfig } from "./config.js";
|
|
20
20
|
import { findLegacyUnits, legacyOwnerLookup } from "./legacy.js";
|
|
21
21
|
import { readLegacySyncEnv } from "./legacy-sync-env.js";
|
|
22
22
|
import { acquireDaemonLock, releaseDaemonLock } from "./lock.js";
|
|
@@ -26,7 +26,6 @@ import { appendPrivate, daemonPaths, ensurePrivateDir, openPrivateAppend } from
|
|
|
26
26
|
import { takeControlRequests } from "./requests.js";
|
|
27
27
|
import { currentHqCloud, hqCloudRunners } from "./hq-cloud.js";
|
|
28
28
|
import { runnerSessionName } from "./remote-control.js";
|
|
29
|
-
import { listSyncRunnerProcesses, readWatchRunnerOwnerStatus, syncHeartbeatTimeoutMs, syncRunnerNodeOptions, watchRunnerOwnerStatusPath, } from "./sync-runner.js";
|
|
30
29
|
import { STOP_ESCALATION_MS, createSupervisor } from "./supervisor.js";
|
|
31
30
|
import { userToolDirs } from "./tools.js";
|
|
32
31
|
export const LOG_ROTATE_BYTES = 5 * 1024 * 1024;
|
|
@@ -214,10 +213,7 @@ function makeLogger(paths, foreground) {
|
|
|
214
213
|
* an on-failure unit down. The daemon's updater handles CLI updates.
|
|
215
214
|
*/
|
|
216
215
|
export function unitProcessEnv(base, unit) {
|
|
217
|
-
|
|
218
|
-
if (unit.service === "sync")
|
|
219
|
-
env.NODE_OPTIONS = syncRunnerNodeOptions(env);
|
|
220
|
-
return env;
|
|
216
|
+
return { ...base, ...unit.env, HQ_DAEMON_UNIT: unit.id, HQ_NO_UPDATE_CHECK: "1" };
|
|
221
217
|
}
|
|
222
218
|
export async function runDaemon(opts) {
|
|
223
219
|
const env = opts.env ?? process.env;
|
|
@@ -262,11 +258,10 @@ export async function runDaemon(opts) {
|
|
|
262
258
|
return legacyCache.units;
|
|
263
259
|
};
|
|
264
260
|
let warnedMissingPin = null;
|
|
265
|
-
const daemonSyncRunnerPids = new Set();
|
|
266
261
|
const plan = () => {
|
|
267
262
|
const config = readDaemonConfig(paths.configPath);
|
|
268
263
|
const legacy = legacyUnits();
|
|
269
|
-
const configured = config.hqRoot ??
|
|
264
|
+
const configured = config.hqRoot ?? env.HQ_ROOT?.trim() ?? undefined;
|
|
270
265
|
const hqRoot = configured && fs.existsSync(configured) ? configured : undefined;
|
|
271
266
|
// Re-picked every reconcile, so a newer hq-cloud from the updater is used as soon as it lands.
|
|
272
267
|
const hqCloud = currentHqCloud(paths.root);
|
|
@@ -278,33 +273,7 @@ export async function runDaemon(opts) {
|
|
|
278
273
|
}
|
|
279
274
|
}
|
|
280
275
|
const runners = hqCloud ? hqCloudRunners(hqCloud.root) : { syncRunner: null, heartbeat: null, sessionHost: null };
|
|
281
|
-
|
|
282
|
-
const isSignedIn = signedIn(env, home, log);
|
|
283
|
-
const syncOwner = effectiveSyncOwner(config, machineType);
|
|
284
|
-
let syncRunnerProcesses = [];
|
|
285
|
-
let syncRunnerOwnerPid = null;
|
|
286
|
-
if (machineType === "desktop" &&
|
|
287
|
-
syncOwner === "daemon" &&
|
|
288
|
-
services.includes("sync") &&
|
|
289
|
-
!config.syncPaused &&
|
|
290
|
-
hqRoot &&
|
|
291
|
-
isSignedIn) {
|
|
292
|
-
const processList = listSyncRunnerProcesses(platform);
|
|
293
|
-
if (!processList.ok) {
|
|
294
|
-
syncRunnerProcesses = null;
|
|
295
|
-
log("warn", `${processList.error}; sync will wait before taking ownership`);
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
syncRunnerProcesses = processList.processes;
|
|
299
|
-
try {
|
|
300
|
-
syncRunnerOwnerPid = readWatchRunnerOwnerStatus(watchRunnerOwnerStatusPath(hqRoot, home, env))?.pid ?? null;
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
log("warn", `could not verify the sync watch-owner status: ${error instanceof Error ? error.message : String(error)}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return planUnits(services, {
|
|
276
|
+
return planUnits(enabledServices(machineType, config), {
|
|
308
277
|
machineType,
|
|
309
278
|
platform,
|
|
310
279
|
home,
|
|
@@ -313,20 +282,14 @@ export async function runDaemon(opts) {
|
|
|
313
282
|
heartbeatRunnerPath: runners.heartbeat,
|
|
314
283
|
sessionHostRunnerPath: runners.sessionHost,
|
|
315
284
|
legacySyncEnv: readLegacySyncEnv(platform),
|
|
316
|
-
signedIn:
|
|
285
|
+
signedIn: signedIn(env, home, log),
|
|
317
286
|
kitConfigured: fs.existsSync(agentKitPaths(home, env).kitConfigPath),
|
|
318
|
-
syncPaused: config.syncPaused === true,
|
|
319
|
-
syncOwner,
|
|
320
287
|
desktopAppManagesSync: desktopAppManagesSync({
|
|
321
288
|
machineType,
|
|
322
289
|
host: opts.host,
|
|
323
290
|
legacy,
|
|
324
291
|
appRunning: () => desktopAppRunning(platform),
|
|
325
292
|
}),
|
|
326
|
-
syncRunnerProcesses,
|
|
327
|
-
syncRunnerOwnerPid,
|
|
328
|
-
daemonSyncRunnerPids,
|
|
329
|
-
syncHeartbeatTimeoutMs: syncHeartbeatTimeoutMs(env),
|
|
330
293
|
bots: listLocalBots(),
|
|
331
294
|
legacyOwner: legacyOwnerLookup(legacy),
|
|
332
295
|
cliSelfUpdatable: updatable,
|
|
@@ -348,27 +311,20 @@ export async function runDaemon(opts) {
|
|
|
348
311
|
: command.type === "node"
|
|
349
312
|
? [opts.nodeBinary, [command.script, ...command.args]]
|
|
350
313
|
: [command.file, command.args];
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
child.stderr?.pipe(logStream, { end: false });
|
|
366
|
-
child.on("close", () => {
|
|
367
|
-
if (child.pid !== undefined)
|
|
368
|
-
daemonSyncRunnerPids.delete(child.pid);
|
|
369
|
-
logStream.end();
|
|
370
|
-
});
|
|
371
|
-
return child;
|
|
314
|
+
const fd = openPrivateAppend(paths.unitLogPath(unit.id));
|
|
315
|
+
try {
|
|
316
|
+
const cwd = unit.env.HQ_ROOT && fs.existsSync(unit.env.HQ_ROOT) ? unit.env.HQ_ROOT : home;
|
|
317
|
+
const child = spawn(file, args, {
|
|
318
|
+
cwd,
|
|
319
|
+
env: unitProcessEnv(env, unit),
|
|
320
|
+
stdio: ["ignore", fd, fd],
|
|
321
|
+
windowsHide: true,
|
|
322
|
+
});
|
|
323
|
+
return child;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
fs.closeSync(fd);
|
|
327
|
+
}
|
|
372
328
|
};
|
|
373
329
|
// Set once the shutdown path exists below; a restart request before then is dropped.
|
|
374
330
|
let restartDaemon = () => { };
|
|
@@ -390,21 +346,6 @@ export async function runDaemon(opts) {
|
|
|
390
346
|
restartDaemon: () => restartDaemon(),
|
|
391
347
|
writeState: (state) => writeJsonAtomic(paths.statePath, state),
|
|
392
348
|
log,
|
|
393
|
-
syncOwnerBlockReason: () => {
|
|
394
|
-
const config = readDaemonConfig(paths.configPath);
|
|
395
|
-
const hqRoot = config.hqRoot ?? env.HQ_ROOT?.trim();
|
|
396
|
-
if (!hqRoot)
|
|
397
|
-
return undefined;
|
|
398
|
-
const statusPath = watchRunnerOwnerStatusPath(hqRoot, home, env);
|
|
399
|
-
try {
|
|
400
|
-
const owner = readWatchRunnerOwnerStatus(statusPath);
|
|
401
|
-
return owner ? `sync owned by ${owner.owner} pid ${owner.pid}` : undefined;
|
|
402
|
-
}
|
|
403
|
-
catch (err) {
|
|
404
|
-
log("error", `could not read sync owner status at ${statusPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
349
|
});
|
|
409
350
|
return await new Promise((resolve) => {
|
|
410
351
|
let stopping = false;
|