@indigoai-us/hq-cli 5.342.12 → 5.342.14
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 +17 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +9 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +9 -0
- package/assets/scaffold/core/scripts/hq-job-probe.sh +9 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +9 -0
- package/dist/commands/agent-kit.d.ts +2 -0
- package/dist/commands/agent-kit.js +23 -17
- package/dist/commands/agent-mcp.d.ts +2 -0
- package/dist/commands/agent-mcp.js +21 -15
- package/dist/commands/core-delegate-w1b.js +19 -10
- package/dist/commands/core-delegate.d.ts +2 -0
- package/dist/commands/core-delegate.js +16 -4
- package/dist/commands/core-job-probe-native.d.ts +4 -1
- package/dist/commands/core-job-probe-native.js +62 -14
- package/dist/commands/core-job-run-native.js +14 -2
- package/dist/commands/core-jobs-native.js +2 -1
- package/dist/commands/core-ontology-native.js +18 -2
- package/dist/commands/core-worker-share-native.js +2 -1
- package/dist/commands/lanes-message.d.ts +20 -2
- package/dist/commands/lanes-message.js +99 -38
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.d.ts +4 -0
- package/dist/commands/lanes.js +24 -30
- package/dist/commands/mesh.js +2 -1
- package/dist/commands/monitor.d.ts +14 -1
- package/dist/commands/monitor.js +43 -14
- package/dist/lib/core-utils/shell-display-path.d.ts +3 -0
- package/dist/lib/core-utils/shell-display-path.js +12 -0
- package/dist/lib/doctor/checks/anywhere.js +11 -6
- 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/keep-alive-audit.d.ts +3 -0
- package/dist/lib/lanes/keep-alive-audit.js +59 -11
- package/dist/lib/lanes/reconcile.js +46 -3
- package/dist/lib/lanes/types.d.ts +2 -1
- package/dist/utils/resident-package-preload.d.ts +8 -1
- package/dist/utils/resident-package-preload.js +68 -0
- package/dist/utils/run-bundled-script.d.ts +2 -0
- package/dist/utils/run-bundled-script.js +1 -0
- package/dist/utils/windows-spawn.d.ts +2 -0
- package/dist/utils/windows-spawn.js +6 -0
- package/docs/anywhere-runtime.md +3 -1
- package/package.json +4 -4
- package/dist/lib/db/remote-engine.d.ts +0 -58
- package/dist/lib/db/remote-engine.js +0 -87
- package/dist/lib/db/remote-sql.d.ts +0 -22
- package/dist/lib/db/remote-sql.js +0 -36
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.342.14] — 2026-10-04
|
|
6
|
+
- Fixed native HQ subprocess launching and job-lock command resolution on Windows.
|
|
7
|
+
- Fixed Windows delegate/job shell parity while preserving embedded carriage returns and POSIX command/path bytes.
|
|
8
|
+
|
|
9
|
+
## [5.342.13] — 2026-10-04
|
|
10
|
+
- Run independent core migration differential cases with bounded parallel child processes while preserving per-case reports and failures.
|
|
11
|
+
|
|
12
|
+
- Keep resident monitor lane delivery in-process and release the supervisor package lease after its lane-message modules preload.
|
|
13
|
+
- Show `hq daemon install` or `hq daemon restart --daemon` in the runtime-health row when the Unix hqd socket is unreachable, based on whether its service is installed.
|
|
14
|
+
- Run shared typecheck and lint once on pull requests while retaining the Node 20 build and test coverage leg.
|
|
15
|
+
- Honor blocked lead `next_check_at` values behind the existing blocked-resume flag, and report due blocked checks in the keep-alive audit.
|
|
16
|
+
- Release package-use leases for agent kit, agent MCP, mesh daemon, lanes watch and wait, and monitor hook wait only after each resident import graph loads.
|
|
17
|
+
- Share one Node 22 CLI build artifact across the four e2e shards to reduce repeated build work.
|
|
18
|
+
- Remove the unused remote vault database helpers. No command called them.
|
|
19
|
+
- Updated semver and its type definitions, and pinned esbuild to 0.28.2.
|
|
20
|
+
- Combine the live Windows pipe DACL and current-user SID inspection in one PowerShell process in the integration test.
|
|
21
|
+
|
|
5
22
|
## [5.342.12] — 2026-10-04
|
|
6
23
|
|
|
7
24
|
- Record every lane worker resume with its launch time and trigger, and classify watcher, monitor-event, and pending-work wakes separately from keep-alive timer accuracy.
|
|
@@ -35,6 +35,15 @@ die() { echo "hq-delegate-grant: $*" >&2; exit 1; }
|
|
|
35
35
|
|
|
36
36
|
command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
|
|
37
37
|
|
|
38
|
+
# jq.exe on Windows emits CRLF data; remove only its line-ending CR bytes.
|
|
39
|
+
case "${OSTYPE:-}" in
|
|
40
|
+
msys*)
|
|
41
|
+
jq() {
|
|
42
|
+
command jq "$@" | command sed -b 's/\r$//'
|
|
43
|
+
}
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
38
47
|
MANIFEST="" YES=0
|
|
39
48
|
while [ $# -gt 0 ]; do
|
|
40
49
|
case "$1" in
|
|
@@ -40,6 +40,15 @@ die() { echo "hq-delegate-send: $*" >&2; exit 1; }
|
|
|
40
40
|
|
|
41
41
|
command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
|
|
42
42
|
|
|
43
|
+
# jq.exe on Windows emits CRLF data; remove only its line-ending CR bytes.
|
|
44
|
+
case "${OSTYPE:-}" in
|
|
45
|
+
msys*)
|
|
46
|
+
jq() {
|
|
47
|
+
command jq "$@" | command sed -b 's/\r$//'
|
|
48
|
+
}
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
43
52
|
MANIFEST="" SEND=0 HEADLINE="" NOTE=""
|
|
44
53
|
while [ $# -gt 0 ]; do
|
|
45
54
|
case "$1" in
|
|
@@ -120,6 +120,15 @@ done
|
|
|
120
120
|
command -v yq >/dev/null 2>&1 || die "yq is required (mikefarah/yq)"
|
|
121
121
|
command -v jq >/dev/null 2>&1 || die "jq is required"
|
|
122
122
|
|
|
123
|
+
# jq.exe on Windows emits CRLF data; remove only its line-ending CR bytes.
|
|
124
|
+
case "${OSTYPE:-}" in
|
|
125
|
+
msys*)
|
|
126
|
+
jq() {
|
|
127
|
+
command jq "$@" | command sed -b 's/\r$//'
|
|
128
|
+
}
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
123
132
|
iso_now() {
|
|
124
133
|
if [ -n "${HQ_JOB_PROBE_NOW:-}" ]; then
|
|
125
134
|
printf '%s' "$HQ_JOB_PROBE_NOW"
|
|
@@ -40,6 +40,15 @@ command -v jq >/dev/null 2>&1 || {
|
|
|
40
40
|
exit 2
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
# jq.exe on Windows emits CRLF data; remove only its line-ending CR bytes.
|
|
44
|
+
case "${OSTYPE:-}" in
|
|
45
|
+
msys*)
|
|
46
|
+
jq() {
|
|
47
|
+
command jq "$@" | command sed -b 's/\r$//'
|
|
48
|
+
}
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
43
52
|
[ "$#" -ge 1 ] || die_usage
|
|
44
53
|
|
|
45
54
|
collect_files() {
|
|
@@ -24,6 +24,7 @@ import { type KitLogger } from "../lib/agent-kit/log.js";
|
|
|
24
24
|
import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
|
|
25
25
|
import { type KitService } from "../lib/agent-kit/services.js";
|
|
26
26
|
import { type CronRunner, type RebootCronResult, type DetachedSpawn, type KillFn } from "../lib/agent-kit/fallback.js";
|
|
27
|
+
import { type ResidentCommandLeaseOptions } from "../utils/resident-package-preload.js";
|
|
27
28
|
import { type ServiceHostPaths, type ServiceManagerDeps, type ServiceSetResult } from "../lib/service-manager/index.js";
|
|
28
29
|
import type { HeartbeatCredsSource } from "../lib/heartbeat-identity.js";
|
|
29
30
|
export declare class KitError extends Error {
|
|
@@ -83,5 +84,6 @@ export declare function runKitService(service: KitService, rt: KitRuntime): Prom
|
|
|
83
84
|
/** `kit run all`: supervise the kit services as child processes with restart backoff. */
|
|
84
85
|
export declare function runAllKitServices(rt: KitRuntime, host: ServiceHostPaths): Promise<void>;
|
|
85
86
|
export declare function formatFallbackStatus(paths: AgentKitPaths, kill?: KillFn): string[] | null;
|
|
87
|
+
export declare function runAgentKitWithPackageLease<T>(run: () => Promise<T>, options?: ResidentCommandLeaseOptions): Promise<T>;
|
|
86
88
|
export declare function registerAgentKitCommand(agent: Command): void;
|
|
87
89
|
//# sourceMappingURL=agent-kit.d.ts.map
|
|
@@ -33,6 +33,7 @@ import { runInboxLoop } from "../lib/agent-kit/run/inbox.js";
|
|
|
33
33
|
import { superviseKitServices } from "../lib/agent-kit/run/supervisor.js";
|
|
34
34
|
import { fallbackStatus, rebootCrontabLine, ensureRebootCrontab, shouldUseFallback, startFallback, stopFallback, } from "../lib/agent-kit/fallback.js";
|
|
35
35
|
import { serviceLogPath } from "../lib/agent-kit/paths.js";
|
|
36
|
+
import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
|
|
36
37
|
import { spawn } from "node:child_process";
|
|
37
38
|
import { pendingInbox } from "../lib/agent-kit/inbox-state.js";
|
|
38
39
|
import { buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_MS, describeWake, fireWake, readWakeConfig, validateWakeUrl, writeWakeConfig, } from "../lib/agent-kit/wake.js";
|
|
@@ -242,6 +243,9 @@ function fail(err) {
|
|
|
242
243
|
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
243
244
|
process.exit(1);
|
|
244
245
|
}
|
|
246
|
+
export function runAgentKitWithPackageLease(run, options) {
|
|
247
|
+
return runResidentCommandWithPackageLease("agent-kit", run, options);
|
|
248
|
+
}
|
|
245
249
|
export function registerAgentKitCommand(agent) {
|
|
246
250
|
const kit = agent
|
|
247
251
|
.command("kit")
|
|
@@ -453,28 +457,30 @@ export function registerAgentKitCommand(agent) {
|
|
|
453
457
|
.command("run <service>")
|
|
454
458
|
.description(`Run one kit service in the foreground (${KIT_SERVICES.join("|")}), or \`all\` to supervise every service in one process`)
|
|
455
459
|
.action(async (service) => {
|
|
456
|
-
if (service
|
|
460
|
+
if (service !== "all" && !isKitService(service)) {
|
|
461
|
+
console.error(chalk.red(`Unknown kit service "${service}". Expected one of: all, ${KIT_SERVICES.join(", ")}`));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
await runAgentKitWithPackageLease(async () => {
|
|
465
|
+
if (service === "all") {
|
|
466
|
+
try {
|
|
467
|
+
const paths = agentKitPaths(os.homedir(), process.env);
|
|
468
|
+
const rt = buildKitRuntime("all");
|
|
469
|
+
await runAllKitServices(rt, resolveServiceHost(paths));
|
|
470
|
+
process.exit(0);
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
fail(err);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
457
476
|
try {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
await runAllKitServices(rt, resolveServiceHost(paths));
|
|
461
|
-
process.exit(0);
|
|
477
|
+
const rt = buildKitRuntime(service);
|
|
478
|
+
await runKitService(service, rt);
|
|
462
479
|
}
|
|
463
480
|
catch (err) {
|
|
464
481
|
fail(err);
|
|
465
482
|
}
|
|
466
|
-
}
|
|
467
|
-
if (!isKitService(service)) {
|
|
468
|
-
console.error(chalk.red(`Unknown kit service "${service}". Expected one of: all, ${KIT_SERVICES.join(", ")}`));
|
|
469
|
-
process.exit(1);
|
|
470
|
-
}
|
|
471
|
-
try {
|
|
472
|
-
const rt = buildKitRuntime(service);
|
|
473
|
-
await runKitService(service, rt);
|
|
474
|
-
}
|
|
475
|
-
catch (err) {
|
|
476
|
-
fail(err);
|
|
477
|
-
}
|
|
483
|
+
});
|
|
478
484
|
});
|
|
479
485
|
}
|
|
480
486
|
//# sourceMappingURL=agent-kit.js.map
|
|
@@ -13,6 +13,7 @@ import { McpServer } from "../lib/agent-kit/mcp/jsonrpc.js";
|
|
|
13
13
|
import { type HqRunResult, type McpToolClients } from "../lib/agent-kit/mcp/tools.js";
|
|
14
14
|
import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
|
|
15
15
|
import type { ExternalMachineCreds } from "../lib/agent-kit/creds.js";
|
|
16
|
+
import { type ResidentCommandLeaseOptions } from "../utils/resident-package-preload.js";
|
|
16
17
|
export { readBounded } from "../lib/agent-kit/attachment-download.js";
|
|
17
18
|
export declare const AGENT_MCP_SERVER_NAME = "hq-agent";
|
|
18
19
|
export declare const AGENT_MCP_INSTRUCTIONS: string;
|
|
@@ -20,5 +21,6 @@ export declare const HQ_SUBPROCESS_TIMEOUT_MS = 120000;
|
|
|
20
21
|
export declare function defaultRunHq(nodeBinary: string, hqBinary: string, env: NodeJS.ProcessEnv): (args: string[]) => Promise<HqRunResult>;
|
|
21
22
|
export declare function defaultMcpClients(creds: ExternalMachineCreds, credsPath: string, paths?: Pick<AgentKitPaths, "inboxDir" | "attachmentsDir">): McpToolClients;
|
|
22
23
|
export declare function createAgentMcpServer(clients: McpToolClients): McpServer;
|
|
24
|
+
export declare function runAgentMcpWithPackageLease<T>(run: () => Promise<T>, options?: ResidentCommandLeaseOptions): Promise<T>;
|
|
23
25
|
export declare function registerAgentMcpCommand(agent: Command): void;
|
|
24
26
|
//# sourceMappingURL=agent-mcp.d.ts.map
|
|
@@ -19,6 +19,7 @@ import { buildAgentMcpTools } from "../lib/agent-kit/mcp/tools.js";
|
|
|
19
19
|
import { readKitConfig } from "../lib/agent-kit/kit-config.js";
|
|
20
20
|
import { agentKitPaths } from "../lib/agent-kit/paths.js";
|
|
21
21
|
import { requireExternalCreds } from "./agent-kit.js";
|
|
22
|
+
import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
|
|
22
23
|
// Re-exported for callers that imported it from here before it moved.
|
|
23
24
|
export { readBounded } from "../lib/agent-kit/attachment-download.js";
|
|
24
25
|
export const AGENT_MCP_SERVER_NAME = "hq-agent";
|
|
@@ -102,26 +103,31 @@ export function createAgentMcpServer(clients) {
|
|
|
102
103
|
tools: buildAgentMcpTools(clients),
|
|
103
104
|
});
|
|
104
105
|
}
|
|
106
|
+
export function runAgentMcpWithPackageLease(run, options) {
|
|
107
|
+
return runResidentCommandWithPackageLease("agent-mcp", run, options);
|
|
108
|
+
}
|
|
105
109
|
export function registerAgentMcpCommand(agent) {
|
|
106
110
|
agent
|
|
107
111
|
.command("mcp")
|
|
108
112
|
.description("Serve HQ as a stdio MCP server for the bot framework (whoami, search, files, secrets-exec, dm, inbox, work-mesh)")
|
|
109
113
|
.action(async () => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
await runAgentMcpWithPackageLease(async () => {
|
|
115
|
+
const paths = agentKitPaths(os.homedir(), process.env);
|
|
116
|
+
let creds;
|
|
117
|
+
try {
|
|
118
|
+
creds = requireExternalCreds(paths);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
process.env.HQ_MACHINE_CREDS_FILE = paths.machineCredsPath;
|
|
125
|
+
process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
|
|
126
|
+
process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
|
|
127
|
+
const server = createAgentMcpServer(defaultMcpClients(creds, paths.machineCredsPath, paths));
|
|
128
|
+
process.stderr.write(`hq agent mcp: serving as ${creds.entityUid} (${creds.companySlug})\n`);
|
|
129
|
+
await server.serve(process.stdin, process.stdout);
|
|
130
|
+
});
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
133
|
//# sourceMappingURL=agent-mcp.js.map
|
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
7
|
+
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
7
8
|
import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
8
9
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
10
|
import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
@@ -171,12 +172,20 @@ function parseFlags(args, allowed) {
|
|
|
171
172
|
}
|
|
172
173
|
function usage(name) { return USAGE[name] ?? ""; }
|
|
173
174
|
function usageFailure(name) { err(usage(name)); return 1; }
|
|
175
|
+
function bashPath(value) {
|
|
176
|
+
if (process.platform !== "win32")
|
|
177
|
+
return value;
|
|
178
|
+
const normalized = value.replaceAll("\\", "/");
|
|
179
|
+
const drivePath = /^([A-Za-z]):\/(.*)$/u.exec(normalized);
|
|
180
|
+
return drivePath ? `/${drivePath[1].toLowerCase()}/${drivePath[2]}` : normalized;
|
|
181
|
+
}
|
|
174
182
|
function runShellFallback(name, args, context) {
|
|
183
|
+
const root = context.root ?? context.cwd;
|
|
175
184
|
const result = runBundledScript({
|
|
176
185
|
asset: ASSETS[name],
|
|
177
186
|
args,
|
|
178
187
|
cwd: context.cwd,
|
|
179
|
-
hqRoot:
|
|
188
|
+
hqRoot: bashPath(root),
|
|
180
189
|
captureOutput: true,
|
|
181
190
|
});
|
|
182
191
|
if (result.stdout?.length)
|
|
@@ -392,16 +401,16 @@ function runGrant(args, context) {
|
|
|
392
401
|
if (!manifestPath)
|
|
393
402
|
return fail("grant", "--manifest is required");
|
|
394
403
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
395
|
-
return fail("grant", `manifest not found: ${manifestPath}`);
|
|
404
|
+
return fail("grant", `manifest not found: ${shellDisplayPath(manifestPath)}`);
|
|
396
405
|
let manifest;
|
|
397
406
|
try {
|
|
398
407
|
manifest = readJsonFile(manifestPath);
|
|
399
408
|
}
|
|
400
409
|
catch {
|
|
401
|
-
return fail("grant", `manifest is not valid JSON: ${manifestPath}`);
|
|
410
|
+
return fail("grant", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
|
|
402
411
|
}
|
|
403
412
|
if (manifest === null || manifest === false)
|
|
404
|
-
return fail("grant", `manifest is not valid JSON: ${manifestPath}`);
|
|
413
|
+
return fail("grant", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
|
|
405
414
|
if (!isRecord(manifest))
|
|
406
415
|
return fail("grant", "manifest has no company");
|
|
407
416
|
const company = jqRawPath(manifest, "company");
|
|
@@ -778,13 +787,13 @@ function runSend(args, context) {
|
|
|
778
787
|
if (!manifestPath)
|
|
779
788
|
return fail("send", "--manifest is required");
|
|
780
789
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
781
|
-
return fail("send", `manifest not found: ${manifestPath}`);
|
|
790
|
+
return fail("send", `manifest not found: ${shellDisplayPath(manifestPath)}`);
|
|
782
791
|
const manifest = jsonFile(manifestPath);
|
|
783
792
|
if (!manifest)
|
|
784
|
-
return fail("send", `manifest is not valid JSON: ${manifestPath}`);
|
|
793
|
+
return fail("send", `manifest is not valid JSON: ${shellDisplayPath(manifestPath)}`);
|
|
785
794
|
if (send) {
|
|
786
795
|
const publishedPath = path.join(context.root ?? context.cwd, jqRawPath(manifest, "publication.manifestPath"));
|
|
787
|
-
if (
|
|
796
|
+
if (malformedJsonFile(publishedPath)) {
|
|
788
797
|
return runShellFallback("send", args, context);
|
|
789
798
|
}
|
|
790
799
|
}
|
|
@@ -894,7 +903,7 @@ function runSend(args, context) {
|
|
|
894
903
|
}
|
|
895
904
|
moveFile(stagedPrompt, prompt);
|
|
896
905
|
fs.rmSync(stage, { recursive: true, force: true });
|
|
897
|
-
out(`hq-delegate-send: pickup prompt written: ${prompt}\n`);
|
|
906
|
+
out(`hq-delegate-send: pickup prompt written: ${shellDisplayPath(prompt)}\n`);
|
|
898
907
|
if (!send)
|
|
899
908
|
return 0;
|
|
900
909
|
if (jqRawPath(manifest, "status") !== "verified")
|
|
@@ -1035,12 +1044,12 @@ function runVerify(args, context) {
|
|
|
1035
1044
|
if (!manifestPath)
|
|
1036
1045
|
return fail("verify", "--manifest is required (or --dry-run with --company/--project/--to)");
|
|
1037
1046
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
1038
|
-
return fail("verify", `manifest not found: ${manifestPath}`);
|
|
1047
|
+
return fail("verify", `manifest not found: ${shellDisplayPath(manifestPath)}`);
|
|
1039
1048
|
if (malformedJsonFile(manifestPath))
|
|
1040
1049
|
return runShellFallback("verify", args, context);
|
|
1041
1050
|
const manifest = jsonFile(manifestPath);
|
|
1042
1051
|
if (!manifest)
|
|
1043
|
-
return fail("verify", `jq: error: ${manifestPath}: Invalid JSON`);
|
|
1052
|
+
return fail("verify", `jq: error: ${shellDisplayPath(manifestPath)}: Invalid JSON`);
|
|
1044
1053
|
const lock = `${manifestPath}.send-lock`;
|
|
1045
1054
|
try {
|
|
1046
1055
|
fs.mkdirSync(lock);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Native implementations of the five `hq core delegate` shell companions. */
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import { type SpawnPlan } from "../utils/windows-spawn.js";
|
|
3
4
|
export declare const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
|
|
4
5
|
export type DelegateContext = {
|
|
5
6
|
cwd: string;
|
|
@@ -18,6 +19,7 @@ export type CommandExistsOptions = {
|
|
|
18
19
|
existsSync?: (candidate: string) => boolean;
|
|
19
20
|
};
|
|
20
21
|
export declare function commandExists(name: string, cwd?: string, options?: CommandExistsOptions): boolean;
|
|
22
|
+
export declare function delegateSubprocessSpawnPlan(command: string, args: readonly string[], env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): SpawnPlan;
|
|
21
23
|
export declare function subprocess(command: string, args: string[], cwd: string, options?: {
|
|
22
24
|
input?: Buffer | string;
|
|
23
25
|
stdinFd?: number;
|
|
@@ -4,7 +4,9 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
7
|
+
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
7
8
|
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
9
|
+
import { buildCmdExeSpawnPlan, isWindowsBatchFile } from "../utils/windows-spawn.js";
|
|
8
10
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
11
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
10
12
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
@@ -86,13 +88,23 @@ function commandPath(name, cwd = process.cwd(), options = {}) {
|
|
|
86
88
|
export function commandExists(name, cwd = process.cwd(), options = {}) {
|
|
87
89
|
return commandPath(name, cwd, options) !== undefined;
|
|
88
90
|
}
|
|
91
|
+
export function delegateSubprocessSpawnPlan(command, args, env = process.env, platform = process.platform) {
|
|
92
|
+
return isWindowsBatchFile(command, platform)
|
|
93
|
+
? buildCmdExeSpawnPlan(command, args, env, platform)
|
|
94
|
+
: { cmd: command, args: [...args], shell: false };
|
|
95
|
+
}
|
|
89
96
|
export function subprocess(command, args, cwd, options = {}) {
|
|
90
|
-
const
|
|
97
|
+
const childEnv = { ...process.env, ...options.env };
|
|
98
|
+
const resolvedCommand = process.platform === "win32" ? commandPath(command, cwd, { env: childEnv }) : undefined;
|
|
99
|
+
const spawn = resolvedCommand ? delegateSubprocessSpawnPlan(resolvedCommand, args, childEnv) : { cmd: command, args, shell: false };
|
|
100
|
+
const result = spawnSync(spawn.cmd, spawn.args, {
|
|
91
101
|
cwd,
|
|
92
|
-
env:
|
|
102
|
+
env: childEnv,
|
|
93
103
|
input: options.input,
|
|
94
104
|
maxBuffer: options.maxBuffer,
|
|
95
105
|
encoding: "buffer",
|
|
106
|
+
shell: spawn.shell,
|
|
107
|
+
...(spawn.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
96
108
|
stdio: [options.input === undefined ? options.stdinFd ?? "inherit" : "pipe", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
|
|
97
109
|
});
|
|
98
110
|
if (result.error) {
|
|
@@ -563,11 +575,11 @@ function existingManifest(name, args, context) {
|
|
|
563
575
|
}
|
|
564
576
|
const file = path.resolve(context.cwd, manifestArg);
|
|
565
577
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
566
|
-
writeErr(`${prefix} manifest not found: ${manifestArg}\n`);
|
|
578
|
+
writeErr(`${prefix} manifest not found: ${shellDisplayPath(manifestArg)}\n`);
|
|
567
579
|
return { code: 1, parsed };
|
|
568
580
|
}
|
|
569
581
|
if (!jqValid(file)) {
|
|
570
|
-
writeErr(`${prefix} manifest is not valid JSON: ${manifestArg}\n`);
|
|
582
|
+
writeErr(`${prefix} manifest is not valid JSON: ${shellDisplayPath(manifestArg)}\n`);
|
|
571
583
|
return { code: 1, parsed };
|
|
572
584
|
}
|
|
573
585
|
return { file, parsed, document: readJsonFile(file) };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
export declare const JOB_PROBE_ASSET = "core/scripts/hq-job-probe.sh";
|
|
3
|
+
export declare function displayJobFile(value: string, platform?: NodeJS.Platform): string;
|
|
4
|
+
export declare function displayShellPath(value: string, platform?: NodeJS.Platform): string;
|
|
3
5
|
export declare const JOB_PROBE_USAGE: string;
|
|
4
6
|
export type Checks = {
|
|
5
7
|
runtime: string;
|
|
@@ -30,7 +32,7 @@ export type ProbeDeps = {
|
|
|
30
32
|
isDirectory: (file: string) => boolean;
|
|
31
33
|
realpath: (file: string) => string | undefined;
|
|
32
34
|
readText: (file: string) => string;
|
|
33
|
-
writeState: (file: string, body: string) => void;
|
|
35
|
+
writeState: (file: string, body: string, mode: number) => void;
|
|
34
36
|
writeStderr: (line: string) => void;
|
|
35
37
|
listSecrets: (company?: string) => Promise<{
|
|
36
38
|
code: number;
|
|
@@ -39,6 +41,7 @@ export type ProbeDeps = {
|
|
|
39
41
|
commandExists: (name: string) => boolean;
|
|
40
42
|
fetcher: (url: string, init: RequestInit) => Promise<Response>;
|
|
41
43
|
};
|
|
44
|
+
export declare function hasProbeCommand(name: string, env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, existsSync?: (candidate: string) => boolean): boolean;
|
|
42
45
|
export declare function defaultProbeDeps(root: string, env?: Record<string, string | undefined>): ProbeDeps;
|
|
43
46
|
export declare function runJobProbe(argv: string[], injected?: ProbeDeps): Promise<ProbeResult>;
|
|
44
47
|
export declare function registerCoreJobProbeCommand(job: Command): void;
|
|
@@ -8,7 +8,21 @@ import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
|
8
8
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
9
9
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
10
10
|
import { coreNativeJobsEnabled } from "./core-native-jobs-flag.js";
|
|
11
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
12
|
+
import { planResolvedCommandSpawn } from "../utils/windows-spawn.js";
|
|
11
13
|
export const JOB_PROBE_ASSET = "core/scripts/hq-job-probe.sh";
|
|
14
|
+
export function displayJobFile(value, platform = process.platform) {
|
|
15
|
+
if (platform !== "win32")
|
|
16
|
+
return value;
|
|
17
|
+
return path.win32.resolve(value).replaceAll("\\", "/");
|
|
18
|
+
}
|
|
19
|
+
export function displayShellPath(value, platform = process.platform) {
|
|
20
|
+
if (platform !== "win32")
|
|
21
|
+
return value;
|
|
22
|
+
const normalized = value.replaceAll("\\", "/");
|
|
23
|
+
const drivePath = /^([A-Za-z]):\/(.*)$/u.exec(normalized);
|
|
24
|
+
return drivePath ? `/${drivePath[1].toLowerCase()}/${drivePath[2]}` : normalized;
|
|
25
|
+
}
|
|
12
26
|
export const JOB_PROBE_USAGE = [
|
|
13
27
|
"hq-job-probe.sh — Outpost requirements probe → hq-pro jobs status (US-008).", "",
|
|
14
28
|
"On the Outpost, deterministically checks a job's requirements checklist and",
|
|
@@ -79,8 +93,8 @@ const realpath = (f) => { try {
|
|
|
79
93
|
catch {
|
|
80
94
|
return undefined;
|
|
81
95
|
} };
|
|
82
|
-
function writeState(file, body) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${body}\n`, { mode
|
|
83
|
-
fs.chmodSync(file,
|
|
96
|
+
function writeState(file, body, mode) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${body}\n`, { mode }); try {
|
|
97
|
+
fs.chmodSync(file, mode);
|
|
84
98
|
}
|
|
85
99
|
catch { /* legacy chmod is best-effort */ } }
|
|
86
100
|
function hasCommand(name, env) {
|
|
@@ -97,10 +111,29 @@ function hasCommand(name, env) {
|
|
|
97
111
|
}
|
|
98
112
|
return false;
|
|
99
113
|
}
|
|
114
|
+
export function hasProbeCommand(name, env, platform = process.platform, existsSync = (candidate) => { try {
|
|
115
|
+
return fs.statSync(candidate).isFile();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
} }) {
|
|
120
|
+
return platform === "win32"
|
|
121
|
+
? resolveWindowsExecutable(name, env, existsSync, process.cwd()) !== null
|
|
122
|
+
: hasCommand(name, env);
|
|
123
|
+
}
|
|
100
124
|
function listSecrets(company) {
|
|
101
125
|
// Keep the `hq secrets list` subprocess: no in-process API produces the same names-only table.
|
|
102
126
|
const args = company ? ["secrets", "list", "--company", company] : ["secrets", "list", "--personal"];
|
|
103
|
-
const
|
|
127
|
+
const executable = process.platform === "win32"
|
|
128
|
+
? resolveWindowsExecutable("hq", process.env, (candidate) => { try {
|
|
129
|
+
return fs.statSync(candidate).isFile();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
} }, process.cwd()) ?? "hq"
|
|
134
|
+
: "hq";
|
|
135
|
+
const plan = planResolvedCommandSpawn(executable, args, process.env);
|
|
136
|
+
const r = spawnSync(plan.cmd, plan.args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], shell: plan.shell, ...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}) });
|
|
104
137
|
return Promise.resolve({ code: r.status ?? (r.error ? 127 : 1), stdout: r.stdout ?? "" });
|
|
105
138
|
}
|
|
106
139
|
export function defaultProbeDeps(root, env = process.env) {
|
|
@@ -111,7 +144,7 @@ export function defaultProbeDeps(root, env = process.env) {
|
|
|
111
144
|
statSize, isFile, isDirectory, realpath, readText: (f) => fs.readFileSync(f, "utf8"), writeState,
|
|
112
145
|
writeStderr: (line) => process.stderr.write(line),
|
|
113
146
|
listSecrets: stubs ? async () => { throw new Error("HQ_JOB_TEST_STUB blocked real hq secrets list"); } : listSecrets,
|
|
114
|
-
commandExists: (name) =>
|
|
147
|
+
commandExists: (name) => hasProbeCommand(name, env),
|
|
115
148
|
fetcher: stubs ? async () => { throw new Error("HQ_JOB_TEST_STUB blocked real network fetch"); } : (url, init) => fetch(url, init) };
|
|
116
149
|
}
|
|
117
150
|
const isoNow = (d) => d.env.HQ_JOB_PROBE_NOW || d.now().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -148,9 +181,21 @@ function legacyYamlParseDiagnostics(file, d) {
|
|
|
148
181
|
// The shell evaluates each field with yq before checking the now-empty id;
|
|
149
182
|
// preserve the six parser diagnostics it emits on malformed input.
|
|
150
183
|
const expressions = [".id // \"\"", ".name // \"\"", ".runtime // \"\"", ".requirements.runtime // \"\"", ".requirements.company // \"\"", ".requirements.cwd // .cwd // \"\""];
|
|
151
|
-
return expressions.map((expression) =>
|
|
152
|
-
|
|
153
|
-
|
|
184
|
+
return expressions.map((expression) => {
|
|
185
|
+
const executable = process.platform === "win32"
|
|
186
|
+
? resolveWindowsExecutable("yq", d.env, (candidate) => { try {
|
|
187
|
+
return fs.statSync(candidate).isFile();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
191
|
+
} }, d.root) ?? "yq"
|
|
192
|
+
: "yq";
|
|
193
|
+
const plan = planResolvedCommandSpawn(executable, ["-r", expression, file], d.env);
|
|
194
|
+
return spawnSync(plan.cmd, plan.args, {
|
|
195
|
+
cwd: d.root, env: d.env, encoding: "utf8", shell: plan.shell,
|
|
196
|
+
...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
197
|
+
}).stderr ?? "";
|
|
198
|
+
}).join("");
|
|
154
199
|
}
|
|
155
200
|
function findById(id, d) {
|
|
156
201
|
let found;
|
|
@@ -240,8 +285,8 @@ function auth(runtime, d) {
|
|
|
240
285
|
const file = runtime === "claude" ? d.env.HQ_JOB_PROBE_CLAUDE_CRED || path.join(d.home, ".claude/.credentials.json") : d.env.HQ_JOB_PROBE_CODEX_AUTH || path.join(d.home, ".codex/auth.json");
|
|
241
286
|
return (d.statSize(file) ?? 0) > 0 ? "ok" : "missing";
|
|
242
287
|
}
|
|
243
|
-
function writeAttempt(d, id, data) { d.writeState(path.join(d.env.HQ_JOB_PROBE_STATE_DIR || path.join(d.home, ".hq/jobs/probes"), id, "last-attempt.json"), compact(data)); }
|
|
244
|
-
function writeCache(d, id, readiness, source) { d.writeState(path.join(d.env.HQ_JOB_STATUS_CACHE_DIR || path.join(d.home, ".hq/jobs/status-cache"), `${id}.json`), compact({ job_id: id, readiness, updated_at: isoNow(d), source })); }
|
|
288
|
+
function writeAttempt(d, id, data) { d.writeState(path.join(d.env.HQ_JOB_PROBE_STATE_DIR || path.join(d.home, ".hq/jobs/probes"), id, "last-attempt.json"), compact(data), 0o600); }
|
|
289
|
+
function writeCache(d, id, readiness, source) { d.writeState(path.join(d.env.HQ_JOB_STATUS_CACHE_DIR || path.join(d.home, ".hq/jobs/status-cache"), `${id}.json`), compact({ job_id: id, readiness, updated_at: isoNow(d), source }), 0o600); }
|
|
245
290
|
function resolveCwd(root, cwd, d) {
|
|
246
291
|
const canon = d.realpath(root) ?? path.resolve(root), candidate = path.resolve(root, cwd), file = d.realpath(candidate) ?? candidate;
|
|
247
292
|
return { file, allowed: file === canon || file.startsWith(`${canon}${path.sep}`) };
|
|
@@ -293,13 +338,13 @@ async function probeFile(file, dry, d) {
|
|
|
293
338
|
catch (e) {
|
|
294
339
|
const rawStderr = legacyYamlParseDiagnostics(file, d);
|
|
295
340
|
if (rawStderr)
|
|
296
|
-
throw new Fatal(`job missing id: ${file}`, 2, "hq-job-probe", rawStderr);
|
|
341
|
+
throw new Fatal(`job missing id: ${displayShellPath(file)}`, 2, "hq-job-probe", rawStderr);
|
|
297
342
|
throw new Fatal(`invalid job YAML: ${file}${e instanceof Error ? `: ${e.message}` : ""}`);
|
|
298
343
|
}
|
|
299
344
|
const id = scalar(job.id), name = scalar(job.name), runtime = scalar(nested(job, "requirements", "runtime")) || scalar(job.runtime);
|
|
300
345
|
const company = scalar(nested(job, "requirements", "company")), cwd = scalar(nested(job, "requirements", "cwd") ?? job.cwd);
|
|
301
346
|
if (!id)
|
|
302
|
-
throw new Fatal(`job missing id: ${file}`);
|
|
347
|
+
throw new Fatal(`job missing id: ${displayShellPath(file)}`);
|
|
303
348
|
if (!runtime)
|
|
304
349
|
throw new Fatal(`job missing runtime: ${file}`);
|
|
305
350
|
const at = isoNow(d), actions = [], authState = auth(runtime, d);
|
|
@@ -411,7 +456,7 @@ async function probeFile(file, dry, d) {
|
|
|
411
456
|
}
|
|
412
457
|
}
|
|
413
458
|
}
|
|
414
|
-
return { code: ingestFailed ? 3 : blocked ? 1 : 0, value: { job_id: id, name, job_file: file, readiness: finalReadiness, probed_at: at, ingest, http, dry_run: dry, checks, next_actions: finalActions } };
|
|
459
|
+
return { code: ingestFailed ? 3 : blocked ? 1 : 0, value: { job_id: id, name, job_file: displayJobFile(file), readiness: finalReadiness, probed_at: at, ingest, http, dry_run: dry, checks, next_actions: finalActions } };
|
|
415
460
|
}
|
|
416
461
|
function parseArgs(argv) {
|
|
417
462
|
const a = [...argv];
|
|
@@ -470,7 +515,7 @@ export async function runJobProbe(argv, injected) {
|
|
|
470
515
|
return { code: 2, stdout: "", stderr: "hq-job-probe: --all does not take a job argument\n" };
|
|
471
516
|
const files = allFiles(root);
|
|
472
517
|
if (!files.length)
|
|
473
|
-
return { code: 2, stdout: "", stderr: `hq-job-probe: no job YAML found under personal/jobs or companies/*/jobs in ${root}\n` };
|
|
518
|
+
return { code: 2, stdout: "", stderr: `hq-job-probe: no job YAML found under personal/jobs or companies/*/jobs in ${displayShellPath(root)}\n` };
|
|
474
519
|
let code = 0;
|
|
475
520
|
try {
|
|
476
521
|
for (const f of files) {
|
|
@@ -500,7 +545,10 @@ export async function runJobProbe(argv, injected) {
|
|
|
500
545
|
// then emits the ordinary not-found error after the process substitution
|
|
501
546
|
// closes. Preserve both stderr lines for byte parity.
|
|
502
547
|
if (x instanceof Fatal && x.message.startsWith("duplicate job id '")) {
|
|
503
|
-
|
|
548
|
+
const shellMessage = process.platform === "win32"
|
|
549
|
+
? x.message.replace(/([A-Za-z]):[\\/]/gu, (_match, drive) => "/" + drive.toLowerCase() + "/").replaceAll("\\", "/")
|
|
550
|
+
: x.message;
|
|
551
|
+
return { code: x.code, stdout: "", stderr: `${x.prefix}: ${shellMessage}\n${x.prefix}: job id not found in registry: ${opts.target}\n` };
|
|
504
552
|
}
|
|
505
553
|
return failure(x);
|
|
506
554
|
}
|
|
@@ -2,6 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
|
|
6
|
+
import { planResolvedCommandSpawn } from "../utils/windows-spawn.js";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
6
8
|
import { JSON_SCHEMA, load } from "js-yaml";
|
|
7
9
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
@@ -256,14 +258,24 @@ export async function spawnProcess(request) {
|
|
|
256
258
|
}
|
|
257
259
|
/** Holds the same kernel flock(2) lock used by the shell companion. */
|
|
258
260
|
export async function acquireFlock(file, env = process.env) {
|
|
259
|
-
const binary = env.HQ_JOB_FLOCK_BIN ||
|
|
261
|
+
const binary = env.HQ_JOB_FLOCK_BIN || (process.platform === "win32"
|
|
262
|
+
? resolveWindowsExecutable("flock", env, (candidate) => { try {
|
|
263
|
+
return fs.statSync(candidate).isFile();
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
} }, process.cwd())
|
|
268
|
+
: pathExecutable("flock", env));
|
|
260
269
|
if (!binary)
|
|
261
270
|
throw new Error("Outpost/Linux requires util-linux flock(1) for serialized job runs");
|
|
262
271
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
263
272
|
return await new Promise((resolve, reject) => {
|
|
264
|
-
const
|
|
273
|
+
const spawnPlan = planResolvedCommandSpawn(binary, ["-x", file, process.execPath, "-e", LOCK_HOLDER], env);
|
|
274
|
+
const child = spawn(spawnPlan.cmd, spawnPlan.args, {
|
|
265
275
|
env,
|
|
266
276
|
stdio: ["pipe", "pipe", "pipe"],
|
|
277
|
+
shell: spawnPlan.shell,
|
|
278
|
+
...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
267
279
|
});
|
|
268
280
|
let stdout = "";
|
|
269
281
|
let stderr = "";
|
|
@@ -4,6 +4,7 @@ import { spawnSync } from "node:child_process";
|
|
|
4
4
|
import { JSON_SCHEMA, load, loadAll } from "js-yaml";
|
|
5
5
|
import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
6
6
|
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
7
|
+
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
7
8
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
8
9
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
9
10
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
@@ -972,7 +973,7 @@ function systemdQuote(value) {
|
|
|
972
973
|
}
|
|
973
974
|
function serviceUnit(root, runner, id) {
|
|
974
975
|
const pathValue = systemdQuote(`PATH=${servicePathValue()}`);
|
|
975
|
-
return `# Generated by outpost-jobs-reconcile.sh — disposable derived state. Do not edit.\n[Unit]\nDescription=HQ Outpost scheduled job: ${id}\nDocumentation=file://${root}/core/knowledge/public/hq-core/outpost-jobs-spec.md\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nEnvironment=HQ_ROOT=${root}\nEnvironment=${pathValue}\nExecStart=${runner} --hq-root ${root} --job-id ${id}\nNice=10\n\n[Install]\nWantedBy=default.target\n`;
|
|
976
|
+
return `# Generated by outpost-jobs-reconcile.sh — disposable derived state. Do not edit.\n[Unit]\nDescription=HQ Outpost scheduled job: ${id}\nDocumentation=file://${root}/core/knowledge/public/hq-core/outpost-jobs-spec.md\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nEnvironment=HQ_ROOT=${root}\nEnvironment=${pathValue}\nExecStart=${shellDisplayPath(runner)} --hq-root ${root} --job-id ${id}\nNice=10\n\n[Install]\nWantedBy=default.target\n`;
|
|
976
977
|
}
|
|
977
978
|
function timerUnit(id, onCalendar, randomize, root) {
|
|
978
979
|
return `# Generated by outpost-jobs-reconcile.sh — disposable derived state. Do not edit.\n[Unit]\nDescription=HQ Outpost scheduled job timer: ${id}\nDocumentation=file://${root}/core/knowledge/public/hq-core/outpost-jobs-spec.md\n\n[Timer]\nOnCalendar=${onCalendar}\nPersistent=true\nRandomizedDelaySec=${randomize}\nUnit=hq-job-${id}.service\n\n[Install]\nWantedBy=timers.target\n`;
|