@indigoai-us/hq-cli 5.312.0 → 5.312.1
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 +7 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
- package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +373 -0
- package/assets/scaffold/core/scripts/register-project.sh +262 -0
- package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
- package/dist/command-catalog.generated.d.ts +65 -0
- package/dist/command-catalog.generated.js +80 -0
- package/dist/commands/company-rename-flag.d.ts +4 -0
- package/dist/commands/company-rename-flag.js +7 -0
- package/dist/commands/company-rename.d.ts +4 -0
- package/dist/commands/company-rename.js +90 -0
- package/dist/commands/company.js +2 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +12 -0
- package/dist/commands/core-eval-trigger-native.d.ts +4 -0
- package/dist/commands/core-eval-trigger-native.js +12 -0
- package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
- package/dist/commands/core-migrate-policy-triggers-native.js +378 -0
- package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
- package/dist/commands/core-native-hook-helper-commands.js +56 -0
- package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
- package/dist/commands/core-native-hook-helpers-flag.js +7 -0
- package/dist/commands/core-register-project-native.d.ts +30 -0
- package/dist/commands/core-register-project-native.js +347 -0
- package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
- package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
- package/dist/commands/core.js +7 -0
- package/dist/fast-core.js +10 -3
- package/dist/fast-scaffold.d.ts +2 -0
- package/dist/fast-scaffold.js +11 -0
- package/dist/lib/agents/model-catalog.d.ts +3 -2
- package/dist/lib/company-skill-wrapper.d.ts +0 -8
- package/dist/lib/core-utils/codex-skill-bridge.d.ts +2 -1
- package/dist/lib/deploy-api.d.ts +0 -1
- package/dist/lib/deploy-api.js +1 -1
- package/dist/lib/doctor/checks/work-context.js +9 -1
- package/dist/lib/mesh/client.js +23 -5
- package/dist/lib/mesh/denial-codes.d.ts +5 -0
- package/dist/lib/mesh/denial-codes.js +14 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/doctor.js +11 -1
- package/dist/lib/report-run.d.ts +3 -2
- package/dist/lib/report-templates.d.ts +0 -1
- package/dist/lib/report-templates.js +0 -3
- package/dist/lib/scaffold-cli-floor.d.ts +0 -1
- package/dist/lib/scaffold-cli-floor.js +1 -1
- package/dist/lib/search-index/embed-lock.d.ts +2 -0
- package/dist/lib/search-index/embed-lock.js +10 -1
- package/dist/lib/work-context/contract.d.ts +3 -0
- package/dist/lib/work-context/contract.schema.json +3 -1
- package/dist/lib/work-context/outbox.d.ts +14 -5
- package/dist/lib/work-context/outbox.js +48 -5
- package/dist/lib/work-context/reconcile.js +26 -5
- package/dist/main.d.ts +20 -0
- package/dist/main.js +8 -1
- package/dist/run/discover-schemas.d.ts +2 -1
- package/dist/utils/core-eval-trigger.d.ts +4 -0
- package/dist/utils/core-eval-trigger.js +88 -0
- package/dist/utils/hq-roots.d.ts +0 -2
- package/dist/utils/hq-roots.js +1 -8
- package/dist/utils/incomplete-install-error.d.ts +7 -6
- package/dist/utils/large-file-guard.d.ts +0 -10
- package/dist/utils/large-file-guard.js +1 -1
- package/dist/utils/package-root-diagnostics.d.ts +1 -1
- package/dist/utils/self-update.d.ts +2 -1
- package/package.json +1 -1
package/dist/fast-core.js
CHANGED
|
@@ -59,12 +59,13 @@ export async function runFastCore(argv) {
|
|
|
59
59
|
if (typeof handler !== "function") {
|
|
60
60
|
throw new Error(`Native fast handler ${entry.exportName} was not exported`);
|
|
61
61
|
}
|
|
62
|
+
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
62
63
|
const chunks = [];
|
|
63
|
-
if (
|
|
64
|
+
if (readsStdin) {
|
|
64
65
|
for await (const chunk of process.stdin)
|
|
65
66
|
chunks.push(chunk);
|
|
66
67
|
}
|
|
67
|
-
const code = await handler(parsed.args,
|
|
68
|
+
const code = await handler(parsed.args, readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
68
69
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
69
70
|
// stderr message must survive the native route's process boundary.
|
|
70
71
|
process.exitCode = code;
|
|
@@ -80,12 +81,18 @@ export async function runFastCore(argv) {
|
|
|
80
81
|
: process.cwd();
|
|
81
82
|
const injectRoot = entry.kind === "shell" || entry.kind === "native" &&
|
|
82
83
|
(entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
|
|
84
|
+
const childEnv = {
|
|
85
|
+
...(entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : {}),
|
|
86
|
+
...(entry.name === "migrate-policy-triggers"
|
|
87
|
+
? { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR || process.cwd() }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
83
90
|
const { code } = runBundledScript({
|
|
84
91
|
asset,
|
|
85
92
|
args: parsed.args,
|
|
86
93
|
cwd,
|
|
87
94
|
...(injectRoot ? { hqRoot } : {}),
|
|
88
|
-
|
|
95
|
+
...(Object.keys(childEnv).length ? { env: childEnv } : {}),
|
|
89
96
|
});
|
|
90
97
|
// Propagate verbatim: these scripts use their exit code as their interface.
|
|
91
98
|
process.exit(code);
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export type FastScaffoldScript = {
|
|
|
40
40
|
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
41
41
|
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
42
42
|
readsStdin?: boolean;
|
|
43
|
+
/** Per-invocation stdin policy for handlers whose shell reads conditionally. */
|
|
44
|
+
readsStdinFor?: (args: readonly string[]) => boolean;
|
|
43
45
|
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
44
46
|
requiresNodeNetworkCompatibility?: boolean;
|
|
45
47
|
};
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -184,6 +184,17 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
184
184
|
shellCwd: "live",
|
|
185
185
|
shellHqRoot: "live",
|
|
186
186
|
},
|
|
187
|
+
...[
|
|
188
|
+
{ name: "eval-trigger", modulePath: "./commands/core-eval-trigger-native.js", exportName: "runEvalTriggerFast", asset: "core/scripts/eval-trigger.sh", root: "cwd", shellCwd: "caller", shellHqRoot: "none", readsStdinFor: (args) => args[0] === "--check" },
|
|
189
|
+
{ name: "migrate-policy-triggers", modulePath: "./commands/core-migrate-policy-triggers-native.js", exportName: "runMigratePolicyTriggersFast", asset: "core/scripts/migrate-policy-triggers.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
190
|
+
{ name: "register-project", modulePath: "./commands/core-register-project-native.js", exportName: "runRegisterProjectFast", asset: "core/scripts/register-project.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
191
|
+
{ name: "work-mesh-live-rebind", modulePath: "./commands/core-work-mesh-live-rebind-native.js", exportName: "runWorkMeshLiveRebindFast", asset: "core/scripts/work-mesh-live-rebind.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
192
|
+
].map((entry) => ({
|
|
193
|
+
...entry,
|
|
194
|
+
kind: "native",
|
|
195
|
+
flagKey: "hq-core-native-fast-route",
|
|
196
|
+
familyFlagKey: "core-native-hook-helpers",
|
|
197
|
+
})),
|
|
187
198
|
...["resolve", "pickup", "repo", "secrets", "transfer"].map((name) => ({
|
|
188
199
|
name: `delegate ${name}`,
|
|
189
200
|
kind: "native",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const AGENT_MODEL_PROVIDERS: readonly ["codex", "grok", "claude"];
|
|
2
2
|
export type AgentModelProvider = (typeof AGENT_MODEL_PROVIDERS)[number];
|
|
3
|
-
|
|
3
|
+
interface AgentCatalogModel {
|
|
4
4
|
id: string;
|
|
5
5
|
label: string;
|
|
6
6
|
visibility: string;
|
|
@@ -10,7 +10,7 @@ export interface AgentCatalogModel {
|
|
|
10
10
|
selectable: boolean;
|
|
11
11
|
unavailableReason?: string | null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
interface AgentCatalogProvider {
|
|
14
14
|
models: AgentCatalogModel[];
|
|
15
15
|
defaultModel: string;
|
|
16
16
|
}
|
|
@@ -58,4 +58,5 @@ export declare function validateAgentModelSelection(catalog: AgentModelCatalog,
|
|
|
58
58
|
export declare function modelSelectionErrorMessage(modelId: string, result: Extract<ModelSelectionResult, {
|
|
59
59
|
ok: false;
|
|
60
60
|
}>): string;
|
|
61
|
+
export {};
|
|
61
62
|
//# sourceMappingURL=model-catalog.d.ts.map
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
export interface CompanySkillWrapperOwnershipDiagnostics {
|
|
3
|
-
kind: "directory" | "symlink" | "file" | "other";
|
|
4
|
-
bridgeState: "all_live" | "partial" | "none" | "empty" | "not_applicable";
|
|
5
|
-
markerState: "bound" | "missing" | "invalid" | "unbound" | "unavailable" | "not_applicable";
|
|
6
|
-
linkTarget: "company_skills" | "outside_company_skills" | "unavailable" | "not_applicable";
|
|
7
|
-
platform: "win32" | "darwin" | "linux" | "other";
|
|
8
|
-
nameEncoding: "percent_encoded" | "literal";
|
|
9
|
-
}
|
|
10
2
|
export declare function companySkillWrapperSentryTagsFor(error: unknown): Record<string, string> | undefined;
|
|
11
3
|
export interface SurfaceCompanySkillInput {
|
|
12
4
|
hqRoot: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
type BridgeCommand = "status" | "install" | "doctor";
|
|
2
2
|
export type BridgeOptions = {
|
|
3
3
|
command: BridgeCommand;
|
|
4
4
|
hqRoot: string;
|
|
@@ -28,4 +28,5 @@ export declare function isHqRootLike(candidate: string): boolean;
|
|
|
28
28
|
export declare function outputStyleSlug(name: string): string;
|
|
29
29
|
/** Runs one bridge command; returns the process exit code. */
|
|
30
30
|
export declare function runCodexSkillBridge(options: BridgeOptions, io: BridgeIo): number;
|
|
31
|
+
export {};
|
|
31
32
|
//# sourceMappingURL=codex-skill-bridge.d.ts.map
|
package/dist/lib/deploy-api.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Shared authenticated request path for the hq-deploy control plane. */
|
|
2
2
|
export declare const DEPLOY_API_URL: string;
|
|
3
3
|
export declare function deployHeaders(token: string, json?: boolean, extra?: Record<string, string>): Record<string, string>;
|
|
4
|
-
export declare function deployApiUrl(route: string): string;
|
|
5
4
|
export declare function deployApiRequest(token: string, route: string, method: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<Response>;
|
|
6
5
|
//# sourceMappingURL=deploy-api.d.ts.map
|
package/dist/lib/deploy-api.js
CHANGED
|
@@ -8,7 +8,7 @@ export function deployHeaders(token, json = true, extra = {}) {
|
|
|
8
8
|
...extra,
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
function deployApiUrl(route) {
|
|
12
12
|
return `${DEPLOY_API_URL.replace(/\/+$/, "")}${route}`;
|
|
13
13
|
}
|
|
14
14
|
export async function deployApiRequest(token, route, method, body, extraHeaders = {}) {
|
|
@@ -89,12 +89,20 @@ function runChecks(deps) {
|
|
|
89
89
|
message: `Outbox queued depth=${stats.depth} (acked=${stats.acked}).`,
|
|
90
90
|
});
|
|
91
91
|
const showQuarantineReason = stats.quarantined > 0 && outboxQuarantineStatusEnabled();
|
|
92
|
+
const quarantineDetails = [
|
|
93
|
+
stats.lastQuarantineHttpStatus !== null
|
|
94
|
+
? `HTTP ${stats.lastQuarantineHttpStatus}`
|
|
95
|
+
: "",
|
|
96
|
+
stats.lastQuarantineServerCode ?? "",
|
|
97
|
+
]
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join(" ");
|
|
92
100
|
results.push({
|
|
93
101
|
status: stats.quarantined > 0 ? "WARN" : "PASS",
|
|
94
102
|
checkId: "work-context.outbox-quarantined",
|
|
95
103
|
target: path.join(root, "outbox"),
|
|
96
104
|
message: `Outbox quarantined count=${stats.quarantined}${showQuarantineReason
|
|
97
|
-
? ` (last reason=${stats.lastQuarantineReason ?? "unknown"})`
|
|
105
|
+
? ` (last reason=${stats.lastQuarantineReason ?? "unknown"}${quarantineDetails ? `; ${quarantineDetails}` : ""})`
|
|
98
106
|
: ""}.`,
|
|
99
107
|
});
|
|
100
108
|
// Default-company mode + migration capability (US-017B)
|
package/dist/lib/mesh/client.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* vault HTTP + receipt-shaped responses (US-007A / US-007B).
|
|
5
5
|
*/
|
|
6
6
|
import { listActiveMembershipCompanies, meshJson, requireToken, resolveMeshCompany, } from "./api.js";
|
|
7
|
+
import { isWorkSessionDenialCode } from "./denial-codes.js";
|
|
7
8
|
export { meshJson, requireToken, resolveMeshCompany, listActiveMembershipCompanies };
|
|
8
9
|
export async function openMeshTransport(opts) {
|
|
9
10
|
const token = await requireToken();
|
|
@@ -50,11 +51,28 @@ export function createWorkSessionDeliverer(opts) {
|
|
|
50
51
|
const message = err instanceof Error ? err.message : String(err);
|
|
51
52
|
// meshJson throws on !ok with error text; classify by leading status when present.
|
|
52
53
|
const statusMatch = /^(\d{3})\b/.exec(message);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const errorFields = err && typeof err === "object"
|
|
55
|
+
? err
|
|
56
|
+
: {};
|
|
57
|
+
const status = typeof errorFields.status === "number" &&
|
|
58
|
+
Number.isInteger(errorFields.status)
|
|
59
|
+
? errorFields.status
|
|
60
|
+
: statusMatch
|
|
61
|
+
? Number(statusMatch[1])
|
|
62
|
+
: undefined;
|
|
63
|
+
if (status === 401 || status === 403) {
|
|
64
|
+
const serverCode = isWorkSessionDenialCode(errorFields.code)
|
|
65
|
+
? errorFields.code
|
|
66
|
+
: undefined;
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
retryable: false,
|
|
70
|
+
code: "AUTH_DENIED",
|
|
71
|
+
httpStatus: status,
|
|
72
|
+
...(serverCode ? { serverCode } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (status !== undefined) {
|
|
58
76
|
if (status === 400 || status === 422) {
|
|
59
77
|
return { ok: false, retryable: false, code: "VALIDATION_FAILED" };
|
|
60
78
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Explicit 401/403 codes returned by hq-pro's work-session registration path. */
|
|
2
|
+
export declare const WORK_SESSION_DENIAL_CODES: readonly ["AUTH_REQUIRED", "AGENT_IDENTITY_UNVERIFIED", "IMPERSONATION_FORBIDDEN", "NO_PERSON_ENTITY", "INVALID_COMPANY", "WORK_REGISTRY_DISABLED"];
|
|
3
|
+
export type WorkSessionDenialCode = (typeof WORK_SESSION_DENIAL_CODES)[number];
|
|
4
|
+
export declare function isWorkSessionDenialCode(value: unknown): value is WorkSessionDenialCode;
|
|
5
|
+
//# sourceMappingURL=denial-codes.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Explicit 401/403 codes returned by hq-pro's work-session registration path. */
|
|
2
|
+
export const WORK_SESSION_DENIAL_CODES = [
|
|
3
|
+
"AUTH_REQUIRED",
|
|
4
|
+
"AGENT_IDENTITY_UNVERIFIED",
|
|
5
|
+
"IMPERSONATION_FORBIDDEN",
|
|
6
|
+
"NO_PERSON_ENTITY",
|
|
7
|
+
"INVALID_COMPANY",
|
|
8
|
+
"WORK_REGISTRY_DISABLED",
|
|
9
|
+
];
|
|
10
|
+
export function isWorkSessionDenialCode(value) {
|
|
11
|
+
return typeof value === "string" &&
|
|
12
|
+
WORK_SESSION_DENIAL_CODES.includes(value);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=denial-codes.js.map
|
|
@@ -27,6 +27,8 @@ export interface DaemonDoctorReport {
|
|
|
27
27
|
outboxDepth: number;
|
|
28
28
|
outboxQuarantined?: number;
|
|
29
29
|
outboxLastQuarantineReason?: string;
|
|
30
|
+
outboxLastQuarantineHttpStatus?: number;
|
|
31
|
+
outboxLastQuarantineServerCode?: string;
|
|
30
32
|
lastFlushAt?: string;
|
|
31
33
|
lastFlushResult?: DaemonStateFile["lastFlushResult"];
|
|
32
34
|
unhealthy: boolean;
|
|
@@ -263,6 +263,8 @@ export function collectDaemonDoctor(deps = {}) {
|
|
|
263
263
|
? {
|
|
264
264
|
outboxQuarantined: outbox.quarantined,
|
|
265
265
|
outboxLastQuarantineReason: outbox.lastQuarantineReason ?? undefined,
|
|
266
|
+
outboxLastQuarantineHttpStatus: outbox.lastQuarantineHttpStatus ?? undefined,
|
|
267
|
+
outboxLastQuarantineServerCode: outbox.lastQuarantineServerCode ?? undefined,
|
|
266
268
|
}
|
|
267
269
|
: {}),
|
|
268
270
|
lastFlushAt: state?.lastFlushAt,
|
|
@@ -310,7 +312,15 @@ export function formatDaemonDoctor(report) {
|
|
|
310
312
|
lines.push(`spool depth: ${report.spoolDepth}`, `held: ${report.heldCount}${report.heldCount > 0 ? " (needs_company; not sent as NONE)" : ""}`, `held with no local evidence: ${report.heldWithNoLocalEvidence}`, `outbox depth: ${report.outboxDepth}`);
|
|
311
313
|
}
|
|
312
314
|
if (report.outboxQuarantined !== undefined) {
|
|
313
|
-
|
|
315
|
+
const quarantineDetails = [
|
|
316
|
+
report.outboxLastQuarantineHttpStatus !== undefined
|
|
317
|
+
? `HTTP ${report.outboxLastQuarantineHttpStatus}`
|
|
318
|
+
: "",
|
|
319
|
+
report.outboxLastQuarantineServerCode ?? "",
|
|
320
|
+
]
|
|
321
|
+
.filter(Boolean)
|
|
322
|
+
.join(" ");
|
|
323
|
+
lines.push(`outbox quarantined: ${report.outboxQuarantined} (last reason=${report.outboxLastQuarantineReason ?? "unknown"}${quarantineDetails ? `; ${quarantineDetails}` : ""})`);
|
|
314
324
|
}
|
|
315
325
|
if (report.emitMode !== "legacy" && report.heldCount > 0) {
|
|
316
326
|
lines.push(`held: ${report.heldCount} (needs_company; not sent as NONE)`);
|
package/dist/lib/report-run.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ export type DigestSignal = {
|
|
|
8
8
|
kind: string;
|
|
9
9
|
text: string;
|
|
10
10
|
};
|
|
11
|
-
|
|
11
|
+
type FocusItem = {
|
|
12
12
|
title: string;
|
|
13
13
|
project: string;
|
|
14
14
|
reason: string;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
type ReportRunRecord = {
|
|
17
17
|
generatedAt: string;
|
|
18
18
|
inputs: {
|
|
19
19
|
template: string;
|
|
@@ -58,4 +58,5 @@ export declare function renderDailyDigest(input: {
|
|
|
58
58
|
generatedAt?: string;
|
|
59
59
|
}): DigestPage;
|
|
60
60
|
export declare function writeDigestPage(directory: string, page: DigestPage): void;
|
|
61
|
+
export {};
|
|
61
62
|
//# sourceMappingURL=report-run.d.ts.map
|
|
@@ -3,7 +3,6 @@ export type ReportTemplate = {
|
|
|
3
3
|
path: string;
|
|
4
4
|
body: string;
|
|
5
5
|
};
|
|
6
|
-
export declare function reportPackageRoot(): string;
|
|
7
6
|
export declare function listReportTemplates(root?: string): ReportTemplate[];
|
|
8
7
|
/** Verbatim runbook. Callers substitute `<reportId>` when the id is known. */
|
|
9
8
|
export declare function loadUpdateReportRunbook(root?: string): string;
|
|
@@ -3,9 +3,6 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
6
|
-
export function reportPackageRoot() {
|
|
7
|
-
return PACKAGE_ROOT;
|
|
8
|
-
}
|
|
9
6
|
export function listReportTemplates(root = PACKAGE_ROOT) {
|
|
10
7
|
const dir = path.join(root, "templates", "reports");
|
|
11
8
|
return readdirSync(dir)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import semver from "semver";
|
|
2
|
-
|
|
2
|
+
const SCAFFOLD_CLI_UPGRADE_COMMAND = "npm install -g @indigoai-us/hq-cli@latest";
|
|
3
3
|
const MINIMUM_VERSION_RANGE = /^>=(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
4
4
|
/** Compare an installed CLI version with the scaffold's supported floor. */
|
|
5
5
|
export function compareScaffoldCliFloor(required, currentVersion) {
|
|
@@ -4,6 +4,8 @@ export type EmbedLockDependencies = {
|
|
|
4
4
|
pid: number;
|
|
5
5
|
random: () => string;
|
|
6
6
|
isProcessAlive: (pid: number) => boolean;
|
|
7
|
+
/** Test seam for a contender arriving after mkdir and before owner publication. */
|
|
8
|
+
beforeOwnerPublish?: (ownerFile: string) => void;
|
|
7
9
|
/** Test seam for simulating a competing owner replacing the atomic record. */
|
|
8
10
|
afterOwnerPublish?: (ownerFile: string) => void;
|
|
9
11
|
};
|
|
@@ -184,8 +184,13 @@ function createAndPublishLock(home, dependencies) {
|
|
|
184
184
|
// parity: the script's write_owner_record honored the same variable.
|
|
185
185
|
if (dependencies.env.QMD_FORCE_OWNER_WRITE_FAIL)
|
|
186
186
|
throw new Error('forced owner write failure');
|
|
187
|
+
dependencies.beforeOwnerPublish?.(ownerFile);
|
|
187
188
|
fs.writeFileSync(temporary, `pid=${dependencies.pid}\nts=${dependencies.now()}\nnonce=${nonce}\n`);
|
|
188
|
-
|
|
189
|
+
// renameSync replaces an existing owner on POSIX. A stale reclaim can
|
|
190
|
+
// replace this ownerless directory while this publisher is paused, so
|
|
191
|
+
// publish only if this exact generation still has no owner.
|
|
192
|
+
fs.linkSync(temporary, ownerFile);
|
|
193
|
+
fs.unlinkSync(temporary);
|
|
189
194
|
dependencies.afterOwnerPublish?.(ownerFile);
|
|
190
195
|
const verified = readOwner(directory);
|
|
191
196
|
if (!verified || verified.pid !== dependencies.pid || verified.nonce !== nonce)
|
|
@@ -196,6 +201,10 @@ function createAndPublishLock(home, dependencies) {
|
|
|
196
201
|
catch {
|
|
197
202
|
// The marker identifies only the directory this process created. Do not
|
|
198
203
|
// recursively remove the fixed lock path after a failed publication.
|
|
204
|
+
try {
|
|
205
|
+
fs.unlinkSync(temporary);
|
|
206
|
+
}
|
|
207
|
+
catch { /* absent after link or moved with a stale generation */ }
|
|
199
208
|
fs.rmSync(marker, { recursive: true, force: true });
|
|
200
209
|
removeDirectoryIfEmpty(directory);
|
|
201
210
|
return false;
|
|
@@ -61,6 +61,9 @@ export interface ContextResult {
|
|
|
61
61
|
bindingEpisodeId?: string;
|
|
62
62
|
clientOperationId: string;
|
|
63
63
|
errorCode?: string;
|
|
64
|
+
/** Sanitized HTTP denial details; raw response bodies are never included. */
|
|
65
|
+
httpStatus?: number;
|
|
66
|
+
serverCode?: string;
|
|
64
67
|
/** Why a project was chosen, or why the session is asking. Local consumers only. */
|
|
65
68
|
projectBindReason?: string;
|
|
66
69
|
/** Ranked alternatives when classification is needs_project. */
|
|
@@ -100,7 +100,9 @@
|
|
|
100
100
|
"taskId": { "type": "string", "maxLength": 128 },
|
|
101
101
|
"bindingEpisodeId": { "type": "string", "maxLength": 128 },
|
|
102
102
|
"clientOperationId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
103
|
-
"errorCode": { "type": "string", "maxLength": 64 }
|
|
103
|
+
"errorCode": { "type": "string", "maxLength": 64 },
|
|
104
|
+
"httpStatus": { "type": "number" },
|
|
105
|
+
"serverCode": { "type": "string", "maxLength": 64 }
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { DeliveryState } from "./contract.js";
|
|
6
6
|
import { WORK_CONTEXT_CONTRACT_VERSION } from "./contract.js";
|
|
7
7
|
/** Fields allowed on a durable outbox operation (privacy allowlist). */
|
|
8
|
-
export declare const OUTBOX_ALLOWLIST: readonly ["contractVersion", "operationId", "clientOperationId", "sessionId", "harness", "companyUid", "companySlug", "projectId", "taskId", "companyConfidence", "kind", "digest", "delivery", "createdAt", "updatedAt", "attemptCount", "lastErrorCode", "receiptId", "destinationCompanyUid", "destinationCompanySlug", "nextAttemptAt"];
|
|
8
|
+
export declare const OUTBOX_ALLOWLIST: readonly ["contractVersion", "operationId", "clientOperationId", "sessionId", "harness", "companyUid", "companySlug", "projectId", "taskId", "companyConfidence", "kind", "digest", "delivery", "createdAt", "updatedAt", "attemptCount", "lastErrorCode", "lastErrorHttpStatus", "lastServerErrorCode", "receiptId", "destinationCompanyUid", "destinationCompanySlug", "nextAttemptAt"];
|
|
9
9
|
/** Base delay for outbox retry backoff (attempt 1 → 30s). */
|
|
10
10
|
export declare const OUTBOX_RETRY_BASE_MS = 30000;
|
|
11
11
|
/** Cap for outbox retry backoff (6 hours). */
|
|
@@ -34,6 +34,8 @@ export interface OutboxOperation {
|
|
|
34
34
|
updatedAt: string;
|
|
35
35
|
attemptCount: number;
|
|
36
36
|
lastErrorCode?: string;
|
|
37
|
+
lastErrorHttpStatus?: number;
|
|
38
|
+
lastServerErrorCode?: string;
|
|
37
39
|
receiptId?: string;
|
|
38
40
|
/** Destination company for kind=migrate (source is companyUid). */
|
|
39
41
|
destinationCompanyUid?: string;
|
|
@@ -88,8 +90,13 @@ export declare function markOutboxAcked(operationId: string, root: string, recei
|
|
|
88
90
|
export declare function writeAckedRegisterState(op: OutboxOperation, root: string, receiptId: string, now?: () => Date): void;
|
|
89
91
|
/** Repair older unresolved session projections from acknowledged registrations. */
|
|
90
92
|
export declare function repairAckedRegisterStates(root: string, sessionIds?: ReadonlySet<string>, now?: () => Date): number;
|
|
91
|
-
export declare function markOutboxQueued(operationId: string, root: string, errorCode: string, now?: () => Date, random?: () => number): OutboxOperation | null;
|
|
92
|
-
export declare function markOutboxQuarantined(operationId: string, root: string, errorCode: string, now?: () => Date): OutboxOperation | null;
|
|
93
|
+
export declare function markOutboxQueued(operationId: string, root: string, errorCode: string, now?: () => Date, random?: () => number, details?: OutboxErrorDetails): OutboxOperation | null;
|
|
94
|
+
export declare function markOutboxQuarantined(operationId: string, root: string, errorCode: string, now?: () => Date, details?: OutboxErrorDetails): OutboxOperation | null;
|
|
95
|
+
export interface OutboxErrorDetails {
|
|
96
|
+
httpStatus?: number;
|
|
97
|
+
serverCode?: string;
|
|
98
|
+
}
|
|
99
|
+
export declare function sanitizeOutboxErrorDetails(details?: OutboxErrorDetails): OutboxErrorDetails;
|
|
93
100
|
/**
|
|
94
101
|
* Requeue quarantined outbox operations so the daemon's replay picks them up
|
|
95
102
|
* again. Quarantine is otherwise terminal: after the work-mesh-live gap 10 fix
|
|
@@ -119,16 +126,18 @@ export interface OutboxStats {
|
|
|
119
126
|
queued: number;
|
|
120
127
|
acked: number;
|
|
121
128
|
lastQuarantineReason: string | null;
|
|
129
|
+
lastQuarantineHttpStatus: number | null;
|
|
130
|
+
lastQuarantineServerCode: string | null;
|
|
122
131
|
}
|
|
123
132
|
export declare function outboxStats(root: string): OutboxStats;
|
|
124
133
|
export type DeliverFn = (op: OutboxOperation) => Promise<{
|
|
125
134
|
ok: true;
|
|
126
135
|
receiptId: string;
|
|
127
|
-
} | {
|
|
136
|
+
} | ({
|
|
128
137
|
ok: false;
|
|
129
138
|
retryable: boolean;
|
|
130
139
|
code: string;
|
|
131
|
-
}>;
|
|
140
|
+
} & OutboxErrorDetails)>;
|
|
132
141
|
/**
|
|
133
142
|
* Replay queued (and recover lost-receipt) operations.
|
|
134
143
|
* FIFO per sessionId; bounded parallelism across sessions.
|
|
@@ -8,6 +8,7 @@ import * as path from "node:path";
|
|
|
8
8
|
import { WORK_CONTEXT_CONTRACT_VERSION } from "./contract.js";
|
|
9
9
|
import { atomicWriteJson, ensureOwnerDir } from "./atomic.js";
|
|
10
10
|
import { NotTrackingError } from "./errors.js";
|
|
11
|
+
import { isWorkSessionDenialCode } from "../mesh/denial-codes.js";
|
|
11
12
|
import { readSessionState, stripRegisterReceiptEpisodes, updateSessionState, writeAcknowledgedRegisterBinding, } from "./state.js";
|
|
12
13
|
import { isSafeWorkContextSegment, workContextOutboxDir, workContextOutboxPath, } from "./paths.js";
|
|
13
14
|
/** Fields allowed on a durable outbox operation (privacy allowlist). */
|
|
@@ -29,6 +30,8 @@ export const OUTBOX_ALLOWLIST = [
|
|
|
29
30
|
"updatedAt",
|
|
30
31
|
"attemptCount",
|
|
31
32
|
"lastErrorCode",
|
|
33
|
+
"lastErrorHttpStatus",
|
|
34
|
+
"lastServerErrorCode",
|
|
32
35
|
"receiptId",
|
|
33
36
|
"destinationCompanyUid",
|
|
34
37
|
"destinationCompanySlug",
|
|
@@ -101,6 +104,11 @@ function projectOutbox(op) {
|
|
|
101
104
|
out.companyConfidence = op.companyConfidence;
|
|
102
105
|
if (op.lastErrorCode)
|
|
103
106
|
out.lastErrorCode = op.lastErrorCode;
|
|
107
|
+
if (op.lastErrorHttpStatus !== undefined) {
|
|
108
|
+
out.lastErrorHttpStatus = op.lastErrorHttpStatus;
|
|
109
|
+
}
|
|
110
|
+
if (op.lastServerErrorCode)
|
|
111
|
+
out.lastServerErrorCode = op.lastServerErrorCode;
|
|
104
112
|
if (op.receiptId)
|
|
105
113
|
out.receiptId = op.receiptId;
|
|
106
114
|
if (op.destinationCompanyUid) {
|
|
@@ -337,12 +345,13 @@ export function repairAckedRegisterStates(root, sessionIds, now = () => new Date
|
|
|
337
345
|
}
|
|
338
346
|
return repaired;
|
|
339
347
|
}
|
|
340
|
-
export function markOutboxQueued(operationId, root, errorCode, now = () => new Date(), random = Math.random) {
|
|
348
|
+
export function markOutboxQueued(operationId, root, errorCode, now = () => new Date(), random = Math.random, details) {
|
|
341
349
|
const op = readOutboxOperation(operationId, root);
|
|
342
350
|
if (!op)
|
|
343
351
|
return null;
|
|
344
352
|
op.delivery = "queued";
|
|
345
353
|
op.lastErrorCode = errorCode;
|
|
354
|
+
applyOutboxErrorDetails(op, details);
|
|
346
355
|
op.attemptCount += 1;
|
|
347
356
|
const at = now();
|
|
348
357
|
op.updatedAt = at.toISOString();
|
|
@@ -351,17 +360,40 @@ export function markOutboxQueued(operationId, root, errorCode, now = () => new D
|
|
|
351
360
|
updateOutboxOperation(op, root);
|
|
352
361
|
return op;
|
|
353
362
|
}
|
|
354
|
-
export function markOutboxQuarantined(operationId, root, errorCode, now = () => new Date()) {
|
|
363
|
+
export function markOutboxQuarantined(operationId, root, errorCode, now = () => new Date(), details) {
|
|
355
364
|
const op = readOutboxOperation(operationId, root);
|
|
356
365
|
if (!op)
|
|
357
366
|
return null;
|
|
358
367
|
op.delivery = "quarantined";
|
|
359
368
|
op.lastErrorCode = errorCode;
|
|
369
|
+
applyOutboxErrorDetails(op, details);
|
|
360
370
|
op.attemptCount += 1;
|
|
361
371
|
op.updatedAt = now().toISOString();
|
|
362
372
|
updateOutboxOperation(op, root);
|
|
363
373
|
return op;
|
|
364
374
|
}
|
|
375
|
+
function applyOutboxErrorDetails(op, details) {
|
|
376
|
+
delete op.lastErrorHttpStatus;
|
|
377
|
+
delete op.lastServerErrorCode;
|
|
378
|
+
const safe = sanitizeOutboxErrorDetails(details);
|
|
379
|
+
if (safe.httpStatus !== undefined)
|
|
380
|
+
op.lastErrorHttpStatus = safe.httpStatus;
|
|
381
|
+
if (safe.serverCode !== undefined)
|
|
382
|
+
op.lastServerErrorCode = safe.serverCode;
|
|
383
|
+
}
|
|
384
|
+
export function sanitizeOutboxErrorDetails(details) {
|
|
385
|
+
const safe = {};
|
|
386
|
+
if (details?.httpStatus !== undefined &&
|
|
387
|
+
Number.isInteger(details.httpStatus) &&
|
|
388
|
+
details.httpStatus >= 100 &&
|
|
389
|
+
details.httpStatus <= 599) {
|
|
390
|
+
safe.httpStatus = details.httpStatus;
|
|
391
|
+
}
|
|
392
|
+
if (isWorkSessionDenialCode(details?.serverCode)) {
|
|
393
|
+
safe.serverCode = details.serverCode;
|
|
394
|
+
}
|
|
395
|
+
return safe;
|
|
396
|
+
}
|
|
365
397
|
/**
|
|
366
398
|
* Requeue quarantined outbox operations so the daemon's replay picks them up
|
|
367
399
|
* again. Quarantine is otherwise terminal: after the work-mesh-live gap 10 fix
|
|
@@ -389,6 +421,8 @@ export function requeueQuarantinedOutbox(root, opts = {}) {
|
|
|
389
421
|
op.delivery = "queued";
|
|
390
422
|
op.attemptCount = 0;
|
|
391
423
|
op.lastErrorCode = undefined;
|
|
424
|
+
op.lastErrorHttpStatus = undefined;
|
|
425
|
+
op.lastServerErrorCode = undefined;
|
|
392
426
|
op.nextAttemptAt = undefined;
|
|
393
427
|
op.updatedAt = now().toISOString();
|
|
394
428
|
updateOutboxOperation(op, root);
|
|
@@ -469,6 +503,15 @@ export function outboxStats(root) {
|
|
|
469
503
|
queued: ops.filter((o) => o.delivery === "queued").length,
|
|
470
504
|
acked: ops.filter((o) => o.delivery === "acked").length,
|
|
471
505
|
lastQuarantineReason: latestQuarantine?.lastErrorCode ?? null,
|
|
506
|
+
lastQuarantineHttpStatus: latestQuarantine &&
|
|
507
|
+
Number.isInteger(latestQuarantine.lastErrorHttpStatus) &&
|
|
508
|
+
latestQuarantine.lastErrorHttpStatus >= 100 &&
|
|
509
|
+
latestQuarantine.lastErrorHttpStatus <= 599
|
|
510
|
+
? latestQuarantine.lastErrorHttpStatus
|
|
511
|
+
: null,
|
|
512
|
+
lastQuarantineServerCode: isWorkSessionDenialCode(latestQuarantine?.lastServerErrorCode)
|
|
513
|
+
? latestQuarantine.lastServerErrorCode
|
|
514
|
+
: null,
|
|
472
515
|
};
|
|
473
516
|
}
|
|
474
517
|
function isOutboxDue(op, nowIso) {
|
|
@@ -551,18 +594,18 @@ export async function replayOutbox(root, deliver, opts = {}) {
|
|
|
551
594
|
}
|
|
552
595
|
else if (result.retryable) {
|
|
553
596
|
if (op.attemptCount + 1 >= OUTBOX_MAX_ATTEMPTS) {
|
|
554
|
-
markOutboxQuarantined(op.operationId, root, `${result.code}_MAX_ATTEMPTS`, now);
|
|
597
|
+
markOutboxQuarantined(op.operationId, root, `${result.code}_MAX_ATTEMPTS`, now, result);
|
|
555
598
|
quarantined += 1;
|
|
556
599
|
}
|
|
557
600
|
else {
|
|
558
|
-
markOutboxQueued(op.operationId, root, result.code, now, random);
|
|
601
|
+
markOutboxQueued(op.operationId, root, result.code, now, random, result);
|
|
559
602
|
queued += 1;
|
|
560
603
|
}
|
|
561
604
|
// Stop this session's FIFO on transient failure.
|
|
562
605
|
break;
|
|
563
606
|
}
|
|
564
607
|
else {
|
|
565
|
-
markOutboxQuarantined(op.operationId, root, result.code, now);
|
|
608
|
+
markOutboxQuarantined(op.operationId, root, result.code, now, result);
|
|
566
609
|
quarantined += 1;
|
|
567
610
|
break;
|
|
568
611
|
}
|
|
@@ -14,7 +14,7 @@ import { refineProjectBinding } from "./bind-best.js";
|
|
|
14
14
|
import { rebindNeedsProjectOnPrompt } from "./prompt-rebind.js";
|
|
15
15
|
import { resolveProjectTask, shouldAskAfter } from "./project.js";
|
|
16
16
|
import { deriveRemoteOwnerSlug, deriveRepoIdentityKey } from "./repo-remote.js";
|
|
17
|
-
import { enqueueOutbox, listOutboxOperations, markOutboxAcked, markOutboxQuarantined, markOutboxQueued, replayOutbox, writeAckedRegisterState, } from "./outbox.js";
|
|
17
|
+
import { enqueueOutbox, listOutboxOperations, markOutboxAcked, markOutboxQuarantined, markOutboxQueued, replayOutbox, sanitizeOutboxErrorDetails, writeAckedRegisterState, } from "./outbox.js";
|
|
18
18
|
import { mergeLocalSessionFields, isRegisterReceiptEpisodeId, readSessionState, stripRegisterReceiptEpisodes, writeSessionState, } from "./state.js";
|
|
19
19
|
function resultOf(parts) {
|
|
20
20
|
const out = {
|
|
@@ -38,6 +38,10 @@ function resultOf(parts) {
|
|
|
38
38
|
out.bindingEpisodeId = parts.bindingEpisodeId;
|
|
39
39
|
if (parts.errorCode)
|
|
40
40
|
out.errorCode = parts.errorCode;
|
|
41
|
+
if (parts.httpStatus !== undefined)
|
|
42
|
+
out.httpStatus = parts.httpStatus;
|
|
43
|
+
if (parts.serverCode)
|
|
44
|
+
out.serverCode = parts.serverCode;
|
|
41
45
|
if (parts.projectBindReason)
|
|
42
46
|
out.projectBindReason = parts.projectBindReason;
|
|
43
47
|
if (parts.projectCandidates)
|
|
@@ -974,7 +978,8 @@ export async function reconcileObservation(obs, deps) {
|
|
|
974
978
|
};
|
|
975
979
|
}
|
|
976
980
|
if (delivered.retryable) {
|
|
977
|
-
|
|
981
|
+
const details = sanitizeOutboxErrorDetails(delivered);
|
|
982
|
+
markOutboxQueued(outboxOp.operationId, deps.root, delivered.code, nowFn, undefined, details);
|
|
978
983
|
return {
|
|
979
984
|
result: resultOf({
|
|
980
985
|
kind: "queued",
|
|
@@ -986,15 +991,20 @@ export async function reconcileObservation(obs, deps) {
|
|
|
986
991
|
projectId,
|
|
987
992
|
taskId,
|
|
988
993
|
errorCode: delivered.code,
|
|
994
|
+
...details,
|
|
989
995
|
...bindResultFields,
|
|
990
996
|
}),
|
|
991
997
|
exitCode: EXIT_OK,
|
|
992
|
-
diagnostics: [
|
|
998
|
+
diagnostics: [
|
|
999
|
+
...diagnostics,
|
|
1000
|
+
`transient ${delivered.code}${formatDeliveryDetails(details)}; remains queued`,
|
|
1001
|
+
],
|
|
993
1002
|
statePath,
|
|
994
1003
|
outboxOperationId: outboxOp.operationId,
|
|
995
1004
|
};
|
|
996
1005
|
}
|
|
997
|
-
|
|
1006
|
+
const details = sanitizeOutboxErrorDetails(delivered);
|
|
1007
|
+
markOutboxQuarantined(outboxOp.operationId, deps.root, delivered.code, nowFn, details);
|
|
998
1008
|
return {
|
|
999
1009
|
result: resultOf({
|
|
1000
1010
|
kind: "error",
|
|
@@ -1007,13 +1017,24 @@ export async function reconcileObservation(obs, deps) {
|
|
|
1007
1017
|
projectId,
|
|
1008
1018
|
taskId,
|
|
1009
1019
|
errorCode: delivered.code,
|
|
1020
|
+
...details,
|
|
1010
1021
|
...bindResultFields,
|
|
1011
1022
|
}),
|
|
1012
1023
|
exitCode: EXIT_OK,
|
|
1013
|
-
diagnostics: [
|
|
1024
|
+
diagnostics: [
|
|
1025
|
+
...diagnostics,
|
|
1026
|
+
`quarantined: ${delivered.code}${formatDeliveryDetails(details)}`,
|
|
1027
|
+
],
|
|
1014
1028
|
statePath,
|
|
1015
1029
|
outboxOperationId: outboxOp.operationId,
|
|
1016
1030
|
};
|
|
1017
1031
|
}
|
|
1032
|
+
function formatDeliveryDetails(details) {
|
|
1033
|
+
const parts = [
|
|
1034
|
+
details.httpStatus !== undefined ? `HTTP ${details.httpStatus}` : "",
|
|
1035
|
+
details.serverCode ?? "",
|
|
1036
|
+
].filter(Boolean);
|
|
1037
|
+
return parts.length ? ` (${parts.join(" ")})` : "";
|
|
1038
|
+
}
|
|
1018
1039
|
export { replayOutbox };
|
|
1019
1040
|
//# sourceMappingURL=reconcile.js.map
|