@nowcrew/daemon 0.6.77-beta.4 → 0.6.77-beta.5
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/dist/agent-ability/resolver.js +1 -1
- package/dist/automatic-daemon-installer.js +29 -18
- package/dist/computer-cli.js +4 -3
- package/dist/computer-service.js +26 -5
- package/dist/config.js +10 -2
- package/dist/daemon-registry.js +20 -0
- package/dist/daemon-update-controller.js +1 -0
- package/dist/daemon-update-eligibility.js +2 -0
- package/dist/daemon-updater.js +26 -26
- package/dist/execution-runner.js +6 -3
- package/dist/local-executor.js +2 -0
- package/dist/main.js +20 -12
- package/dist/npm-command.js +9 -0
- package/dist/project-dependencies.js +4 -4
- package/dist/repository-identity.js +2 -0
- package/dist/runner.js +2 -0
- package/dist/storage-layout.js +2 -1
- package/package.json +1 -1
|
@@ -185,7 +185,7 @@ const materializeGit = async (cacheRoot, repositoryUrl, commit, target) => {
|
|
|
185
185
|
await git(["--git-dir", bare, "fetch", "--no-tags", "--prune", "origin", commit]);
|
|
186
186
|
const temp = `${target}.tmp-${randomUUID()}`;
|
|
187
187
|
await rm(temp, { recursive: true, force: true });
|
|
188
|
-
await git(["clone", "--
|
|
188
|
+
await git(["clone", "--shared", bare, temp]);
|
|
189
189
|
await git(["checkout", "--detach", commit], temp);
|
|
190
190
|
await rm(join(temp, ".git"), { recursive: true, force: true });
|
|
191
191
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -10,7 +10,8 @@ import { acquireDaemonInstallationLease, } from "./daemon-installation-lease.js"
|
|
|
10
10
|
import { daemonVersion } from "./machine-info.js";
|
|
11
11
|
import { daemonReleaseProvenance } from "./release-provenance.js";
|
|
12
12
|
import { daemonReleaseChannel, isDaemonReleaseVersion } from "./daemon-release-version.js";
|
|
13
|
-
import {
|
|
13
|
+
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
14
|
+
import { npmInvocation } from "./npm-command.js";
|
|
14
15
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
15
16
|
function profileStorage(platform, userHome, runner) {
|
|
16
17
|
return {
|
|
@@ -178,24 +179,34 @@ export function stableDaemonEntry(target, platform) {
|
|
|
178
179
|
? paths.join(target.npmPrefix, "node_modules", "@nowcrew", "daemon", "dist", "main.js")
|
|
179
180
|
: paths.join(target.npmPrefix, "lib", "node_modules", "@nowcrew", "daemon", "dist", "main.js");
|
|
180
181
|
}
|
|
181
|
-
export async function installExactDaemonPackage(npmPrefix, version, platform, runner, registryUrl) {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
export async function installExactDaemonPackage(npmPrefix, version, platform, runner, registryUrl, nodePath = process.execPath) {
|
|
183
|
+
const registries = daemonRegistryCandidates({
|
|
184
|
+
betaUpdate: daemonReleaseChannel(version) === "beta",
|
|
185
|
+
...(registryUrl === undefined ? {} : { registryUrl }),
|
|
186
|
+
});
|
|
187
|
+
if (registries === null)
|
|
188
|
+
throw new Error("Daemon package installation failed: invalid registry");
|
|
189
|
+
const npm = npmInvocation(platform, nodePath);
|
|
190
|
+
let lastFailure = "";
|
|
191
|
+
for (const registry of registries) {
|
|
192
|
+
const result = await runner(npm.command, [
|
|
193
|
+
...npm.argsPrefix,
|
|
194
|
+
"install",
|
|
195
|
+
"--global",
|
|
196
|
+
"--prefix",
|
|
197
|
+
npmPrefix,
|
|
198
|
+
"--ignore-scripts",
|
|
199
|
+
"--no-audit",
|
|
200
|
+
"--no-fund",
|
|
201
|
+
"--registry",
|
|
202
|
+
registry,
|
|
203
|
+
`@nowcrew/daemon@${version}`,
|
|
204
|
+
]);
|
|
205
|
+
if (result.exitCode === 0)
|
|
206
|
+
return;
|
|
207
|
+
lastFailure = result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`;
|
|
198
208
|
}
|
|
209
|
+
throw new Error(`Daemon package installation failed: ${lastFailure}`);
|
|
199
210
|
}
|
|
200
211
|
export async function waitForServiceRunning(spec, status, options = {}) {
|
|
201
212
|
const timeoutMs = options.timeoutMs ?? 10_000;
|
package/dist/computer-cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { assertProfileAgentsRootUnique, daemonHome, inspectProfileAgentsRoot, li
|
|
|
6
6
|
import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, } from "./computer-service.js";
|
|
7
7
|
import { releaseTagForServerUrl, assertReleaseTagForServerUrl } from "./automatic-install-target.js";
|
|
8
8
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
9
|
+
import { npmInvocation } from "./npm-command.js";
|
|
9
10
|
import { defaultProcessController } from "./execution-journal.js";
|
|
10
11
|
import { inspectJournalLock } from "./execution-journal-lock.js";
|
|
11
12
|
import { detectDaemonLang, formatDaemonText } from "./i18n.js";
|
|
@@ -24,7 +25,7 @@ export function builtDaemonEntry(moduleUrl = import.meta.url) {
|
|
|
24
25
|
: "";
|
|
25
26
|
}
|
|
26
27
|
function defaults() {
|
|
27
|
-
const
|
|
28
|
+
const npm = npmInvocation(process.platform, process.execPath);
|
|
28
29
|
return {
|
|
29
30
|
env: process.env,
|
|
30
31
|
platform: process.platform,
|
|
@@ -35,7 +36,7 @@ function defaults() {
|
|
|
35
36
|
runner: systemCommandRunner,
|
|
36
37
|
...(process.platform === "win32" ? { profileProtector: windowsDpapiProtector } : {}),
|
|
37
38
|
resolveGlobalNodeModules: async () => {
|
|
38
|
-
const result = await systemCommandRunner(
|
|
39
|
+
const result = await systemCommandRunner(npm.command, [...npm.argsPrefix, "root", "--global"]);
|
|
39
40
|
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
40
41
|
throw new Error(`Unable to locate global npm modules: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
41
42
|
}
|
|
@@ -279,7 +280,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
279
280
|
await upgradeDaemon(deps.platform, deps.runner, installation?.npmPrefix, releaseTag, configuredRegistryUrl === undefined
|
|
280
281
|
? restartProfile?.registryUrl
|
|
281
282
|
?? configuredRegistryUrl
|
|
282
|
-
: normalizeDaemonNpmRegistry(configuredRegistryUrl, { allowLoopbackHttp: releaseTag === "beta" }));
|
|
283
|
+
: normalizeDaemonNpmRegistry(configuredRegistryUrl, { allowLoopbackHttp: releaseTag === "beta" }), deps.nodePath);
|
|
283
284
|
}
|
|
284
285
|
catch (error) {
|
|
285
286
|
upgradeError = error;
|
package/dist/computer-service.js
CHANGED
|
@@ -5,6 +5,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
8
|
+
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
9
|
+
import { npmInvocation } from "./npm-command.js";
|
|
8
10
|
const execFileAsync = promisify(execFile);
|
|
9
11
|
const WINDOWS_STOP_TIMEOUT_MS = 5_000;
|
|
10
12
|
const WINDOWS_STOP_POLL_MS = 100;
|
|
@@ -542,10 +544,12 @@ export async function doctorService(spec, profilePrivate, runner = systemCommand
|
|
|
542
544
|
{ name: "service-running", ok: status.running === true, detail: status.running ? "running" : "not running" },
|
|
543
545
|
];
|
|
544
546
|
}
|
|
545
|
-
export function buildUpgradeCommand(platform, npmPrefix, releaseTag = "latest", registryUrl) {
|
|
547
|
+
export function buildUpgradeCommand(platform, npmPrefix, releaseTag = "latest", registryUrl, nodePath = process.execPath) {
|
|
548
|
+
const npm = npmInvocation(platform, nodePath);
|
|
546
549
|
return [
|
|
547
|
-
|
|
550
|
+
npm.command,
|
|
548
551
|
[
|
|
552
|
+
...npm.argsPrefix,
|
|
549
553
|
"install",
|
|
550
554
|
"--global",
|
|
551
555
|
"--ignore-scripts",
|
|
@@ -557,9 +561,26 @@ export function buildUpgradeCommand(platform, npmPrefix, releaseTag = "latest",
|
|
|
557
561
|
],
|
|
558
562
|
];
|
|
559
563
|
}
|
|
560
|
-
export async function upgradeDaemon(platform, runner = systemCommandRunner, npmPrefix, releaseTag = "latest", registryUrl) {
|
|
561
|
-
const
|
|
562
|
-
|
|
564
|
+
export async function upgradeDaemon(platform, runner = systemCommandRunner, npmPrefix, releaseTag = "latest", registryUrl, nodePath = process.execPath) {
|
|
565
|
+
const registries = daemonRegistryCandidates({
|
|
566
|
+
betaUpdate: releaseTag === "beta",
|
|
567
|
+
...(registryUrl === undefined ? {} : { registryUrl }),
|
|
568
|
+
env: process.env,
|
|
569
|
+
});
|
|
570
|
+
if (registries === null)
|
|
571
|
+
throw new Error("Daemon upgrade failed: invalid registry");
|
|
572
|
+
let lastFailure = "";
|
|
573
|
+
for (const registry of registries) {
|
|
574
|
+
const [command, args] = buildUpgradeCommand(platform, npmPrefix, releaseTag, registry, nodePath);
|
|
575
|
+
try {
|
|
576
|
+
await checked(runner, command, args, "Daemon upgrade");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
lastFailure = error instanceof Error ? error.message : String(error);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
throw new Error(lastFailure || "Daemon upgrade failed");
|
|
563
584
|
}
|
|
564
585
|
const WINDOWS_SID_SCRIPT = "[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value";
|
|
565
586
|
export async function hardenWindowsProfile(path, runner = systemCommandRunner) {
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** daemon 配置:从 env 读取,解析 crew CLI 路径 (注入给 agent 的 wrapper 用)。 */
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { detectDaemonLang, translateDaemon } from "./i18n.js";
|
|
@@ -134,10 +134,18 @@ export function loadConfig(env = process.env) {
|
|
|
134
134
|
maxFirstOutputWaitMs: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS", DEFAULT_EXECUTION_LIMITS.maxFirstOutputWaitMs),
|
|
135
135
|
firstOutputGraceMs: positiveIntegerEnv(env, "CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS", DEFAULT_EXECUTION_LIMITS.firstOutputGraceMs),
|
|
136
136
|
});
|
|
137
|
+
const agentsRoot = resolveAgentsRoot(env.CREW_AGENTS_ROOT, homedir());
|
|
138
|
+
const configuredReposRoot = env.NOWWORK_REPOS_ROOT?.trim();
|
|
139
|
+
const reposRoot = configuredReposRoot ? resolve(configuredReposRoot) : undefined;
|
|
140
|
+
const configuredQaReportRoot = env.QA_REPORT_HUB?.trim();
|
|
137
141
|
return {
|
|
138
142
|
serverUrl,
|
|
139
143
|
machineToken,
|
|
140
|
-
agentsRoot
|
|
144
|
+
agentsRoot,
|
|
145
|
+
...(reposRoot === undefined ? {} : { reposRoot }),
|
|
146
|
+
qaReportRoot: configuredQaReportRoot
|
|
147
|
+
? resolve(configuredQaReportRoot)
|
|
148
|
+
: join(dirname(reposRoot ?? agentsRoot), "qa-report-server-v2"),
|
|
141
149
|
cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
|
|
142
150
|
runtimeBin: env.CREW_RUNTIME ?? "claude",
|
|
143
151
|
codexHomeMode: codexHomeModeEnv(env),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
2
|
+
export const DAEMON_PUBLIC_NPM_REGISTRY = normalizeDaemonNpmRegistry();
|
|
3
|
+
export const DAEMON_PUBLIC_NPM_MIRROR = "https://registry.npmmirror.com";
|
|
4
|
+
/** Keep every daemon package operation on the same ordered, channel-safe sources. */
|
|
5
|
+
export function daemonRegistryCandidates(input) {
|
|
6
|
+
try {
|
|
7
|
+
const requested = input.registryUrl ?? input.env?.CREW_NPM_REGISTRY;
|
|
8
|
+
const configured = requested === undefined
|
|
9
|
+
? DAEMON_PUBLIC_NPM_REGISTRY
|
|
10
|
+
: normalizeDaemonNpmRegistry(requested, { allowLoopbackHttp: input.betaUpdate });
|
|
11
|
+
return [...new Set([
|
|
12
|
+
configured,
|
|
13
|
+
DAEMON_PUBLIC_NPM_REGISTRY,
|
|
14
|
+
...(input.betaUpdate ? [DAEMON_PUBLIC_NPM_MIRROR] : []),
|
|
15
|
+
])];
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -30,6 +30,7 @@ export function createDaemonUpdateController(deps) {
|
|
|
30
30
|
targetVersion: message.targetVersion,
|
|
31
31
|
packageRoot: eligibility.packageRoot,
|
|
32
32
|
npmPrefix: eligibility.npmPrefix,
|
|
33
|
+
...(eligibility.registryUrl === undefined ? {} : { registryUrl: eligibility.registryUrl }),
|
|
33
34
|
onInstalling: () => {
|
|
34
35
|
deps.sendStatus({ type: "daemon:update-status", updateId: message.updateId, status: "installing" });
|
|
35
36
|
},
|
|
@@ -109,6 +109,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
|
|
|
109
109
|
eligible: true,
|
|
110
110
|
profileName,
|
|
111
111
|
...installation,
|
|
112
|
+
...(profile.registryUrl === undefined ? {} : { registryUrl: profile.registryUrl }),
|
|
112
113
|
serviceSpec: { ...spec, runtimeProcessIdentity: scope.processIdentity },
|
|
113
114
|
};
|
|
114
115
|
}
|
|
@@ -188,6 +189,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
|
|
|
188
189
|
eligible: true,
|
|
189
190
|
profileName,
|
|
190
191
|
...installation,
|
|
192
|
+
...(profile.registryUrl === undefined ? {} : { registryUrl: profile.registryUrl }),
|
|
191
193
|
serviceSpec: selectedSpec,
|
|
192
194
|
};
|
|
193
195
|
}
|
package/dist/daemon-updater.js
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
3
|
import { systemCommandRunner } from "./computer-service.js";
|
|
4
4
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
5
5
|
import { daemonReleaseChannel, isDaemonReleaseVersion } from "./daemon-release-version.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
function configuredRegistries(betaUpdate) {
|
|
9
|
-
try {
|
|
10
|
-
const configured = process.env.CREW_NPM_REGISTRY === undefined
|
|
11
|
-
? PUBLIC_NPM_REGISTRY
|
|
12
|
-
: normalizeDaemonNpmRegistry(process.env.CREW_NPM_REGISTRY, { allowLoopbackHttp: betaUpdate });
|
|
13
|
-
return betaUpdate ? [...new Set([configured, PUBLIC_NPM_REGISTRY])] : [configured];
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function npmCommand(platform, nodePath) {
|
|
20
|
-
const paths = platform === "win32" ? win32 : posix;
|
|
21
|
-
return paths.join(paths.dirname(nodePath), platform === "win32" ? "npm.cmd" : "npm");
|
|
22
|
-
}
|
|
6
|
+
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
7
|
+
import { npmInvocation } from "./npm-command.js";
|
|
23
8
|
function registryArgs(registry) {
|
|
24
9
|
return ["--registry", registry];
|
|
25
10
|
}
|
|
@@ -51,10 +36,11 @@ async function readPackageVersion(packageRoot) {
|
|
|
51
36
|
const body = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
|
|
52
37
|
return typeof body.version === "string" ? body.version : "";
|
|
53
38
|
}
|
|
54
|
-
async function restoreInstalledVersion(input, runner, command, version, registryArgs) {
|
|
39
|
+
async function restoreInstalledVersion(input, runner, command, commandPrefix, version, registryArgs) {
|
|
55
40
|
if (!isDaemonReleaseVersion(version))
|
|
56
41
|
return false;
|
|
57
42
|
const result = await runner(command, [
|
|
43
|
+
...commandPrefix,
|
|
58
44
|
"install",
|
|
59
45
|
"--global",
|
|
60
46
|
"--prefix",
|
|
@@ -110,7 +96,9 @@ export async function installExactDaemonUpdate(input) {
|
|
|
110
96
|
}
|
|
111
97
|
const runner = input.runner ?? systemCommandRunner;
|
|
112
98
|
const platform = input.platform ?? process.platform;
|
|
113
|
-
const
|
|
99
|
+
const npm = npmInvocation(platform, input.nodePath ?? process.execPath);
|
|
100
|
+
const command = npm.command;
|
|
101
|
+
const commandPrefix = npm.argsPrefix;
|
|
114
102
|
let previousVersion = "";
|
|
115
103
|
try {
|
|
116
104
|
previousVersion = await (input.readInstalledVersion ?? readPackageVersion)(input.packageRoot);
|
|
@@ -124,8 +112,12 @@ export async function installExactDaemonUpdate(input) {
|
|
|
124
112
|
await release();
|
|
125
113
|
return { ok: false, errorCode: "install_failed" };
|
|
126
114
|
}
|
|
127
|
-
const registries =
|
|
128
|
-
|
|
115
|
+
const registries = daemonRegistryCandidates({
|
|
116
|
+
betaUpdate: daemonReleaseChannel(previousVersion) === "beta"
|
|
117
|
+
|| daemonReleaseChannel(input.targetVersion) === "beta",
|
|
118
|
+
...(input.registryUrl === undefined ? {} : { registryUrl: input.registryUrl }),
|
|
119
|
+
env: process.env,
|
|
120
|
+
});
|
|
129
121
|
if (registries === null) {
|
|
130
122
|
await release();
|
|
131
123
|
return { ok: false, errorCode: "install_failed" };
|
|
@@ -139,10 +131,12 @@ export async function installExactDaemonUpdate(input) {
|
|
|
139
131
|
}
|
|
140
132
|
let selectedRegistryArgs = [];
|
|
141
133
|
let installed = false;
|
|
142
|
-
let
|
|
134
|
+
let authFailures = 0;
|
|
135
|
+
let failedAttempts = 0;
|
|
143
136
|
for (const registry of registries) {
|
|
144
137
|
const args = registryArgs(registry);
|
|
145
138
|
const result = await runner(command, [
|
|
139
|
+
...commandPrefix,
|
|
146
140
|
"install",
|
|
147
141
|
"--global",
|
|
148
142
|
"--prefix",
|
|
@@ -158,12 +152,18 @@ export async function installExactDaemonUpdate(input) {
|
|
|
158
152
|
installed = true;
|
|
159
153
|
break;
|
|
160
154
|
}
|
|
155
|
+
failedAttempts += 1;
|
|
161
156
|
if (result !== null && isRegistryAuthFailure(result.stderr))
|
|
162
|
-
|
|
157
|
+
authFailures += 1;
|
|
163
158
|
}
|
|
164
159
|
if (!installed) {
|
|
165
160
|
await release();
|
|
166
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
errorCode: failedAttempts > 0 && authFailures === failedAttempts
|
|
164
|
+
? "registry_auth_failed"
|
|
165
|
+
: "install_failed",
|
|
166
|
+
};
|
|
167
167
|
}
|
|
168
168
|
let installedVersion = "";
|
|
169
169
|
try {
|
|
@@ -173,7 +173,7 @@ export async function installExactDaemonUpdate(input) {
|
|
|
173
173
|
// Version verification is authoritative; unreadable package metadata is a mismatch.
|
|
174
174
|
}
|
|
175
175
|
if (installedVersion !== input.targetVersion) {
|
|
176
|
-
const restored = await restoreInstalledVersion(input, runner, command, previousVersion, selectedRegistryArgs);
|
|
176
|
+
const restored = await restoreInstalledVersion(input, runner, command, commandPrefix, previousVersion, selectedRegistryArgs);
|
|
177
177
|
if (!restored) {
|
|
178
178
|
console.error(`[daemon-update] version verification failed; restore of ${previousVersion} was unsuccessful`);
|
|
179
179
|
}
|
package/dist/execution-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
|
|
4
4
|
import { JournalConflictError } from "./execution-journal.js";
|
|
5
5
|
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
@@ -524,10 +524,11 @@ export async function runExecution(config, input, dependencies) {
|
|
|
524
524
|
if (dependencies.abilityRelease !== undefined) {
|
|
525
525
|
await cancellable(dependencies.abilityRelease.apply(config.agentsRoot, spec.agent.handle, spec.agent.abilityRelease), dependencies.cancellation);
|
|
526
526
|
}
|
|
527
|
+
const reposRoot = config.reposRoot ?? join(config.agentsRoot, "repos");
|
|
527
528
|
const preparedProjectDependencies = await cancellable(dependencies.projectDependencies.prepare({
|
|
528
529
|
trainingRoot: join(config.agentsRoot, spec.agent.handle, "training"),
|
|
529
|
-
reposRoot
|
|
530
|
-
lockRoot: join(
|
|
530
|
+
reposRoot,
|
|
531
|
+
lockRoot: join(dirname(resolve(reposRoot)), ".crew", "project-dependencies"),
|
|
531
532
|
}), dependencies.cancellation);
|
|
532
533
|
trainingProjectContext = Object.freeze({
|
|
533
534
|
projects: Object.freeze(preparedProjectDependencies.map((project) => Object.freeze({
|
|
@@ -862,6 +863,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
862
863
|
token: credential.token,
|
|
863
864
|
agentId: credential.agentId,
|
|
864
865
|
agentsRoot: config.agentsRoot,
|
|
866
|
+
...(config.reposRoot === undefined ? {} : { reposRoot: config.reposRoot }),
|
|
867
|
+
...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
|
|
865
868
|
cliPath: config.cliPath,
|
|
866
869
|
providerConfig,
|
|
867
870
|
codexHomeMode: config.codexHomeMode,
|
package/dist/local-executor.js
CHANGED
|
@@ -466,6 +466,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
466
466
|
CREW_TOKEN: input.launch.token,
|
|
467
467
|
CREW_CHANNEL: input.channelId,
|
|
468
468
|
CREW_HOME: workspace.dir,
|
|
469
|
+
...(input.launch.reposRoot === undefined ? {} : { NOWWORK_REPOS_ROOT: input.launch.reposRoot }),
|
|
470
|
+
...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
|
|
469
471
|
// Expose only the training root; ability-specific workspace layout stays
|
|
470
472
|
// owned by the training package rather than the NowWork daemon.
|
|
471
473
|
QA_RUNTIME_ROOT: join(workspace.dir, "training"),
|
package/dist/main.js
CHANGED
|
@@ -117,6 +117,23 @@ async function main() {
|
|
|
117
117
|
}
|
|
118
118
|
const provenance = daemonReleaseProvenance(daemonVersion());
|
|
119
119
|
assertReleaseTagForServerUrl(config.serverUrl, provenance.releaseTag);
|
|
120
|
+
const storageRuntime = values.profile === undefined
|
|
121
|
+
? null
|
|
122
|
+
: await loadStorageRuntime({
|
|
123
|
+
profile: values.profile,
|
|
124
|
+
serverUrl: config.serverUrl,
|
|
125
|
+
userHome: homedir(),
|
|
126
|
+
platform: process.platform,
|
|
127
|
+
environment: process.env,
|
|
128
|
+
});
|
|
129
|
+
const runtimeConfig = storageRuntime === null
|
|
130
|
+
? config
|
|
131
|
+
: Object.freeze({
|
|
132
|
+
...config,
|
|
133
|
+
agentsRoot: storageRuntime.layout.agentItemsRoot,
|
|
134
|
+
reposRoot: storageRuntime.layout.repoRoot,
|
|
135
|
+
qaReportRoot: storageRuntime.layout.qaReportRoot,
|
|
136
|
+
});
|
|
120
137
|
if (cmd === "serve") {
|
|
121
138
|
process.stdout.write(formatDaemonLogLine(`🛰️ crew-daemon v${daemonVersion()} (cli v${cliVersion()}, ${provenance.releaseChannel}/${provenance.releaseTag}) ${td("resident, connecting to")} ${config.serverUrl} ${td("control plane")}...`) + "\n");
|
|
122
139
|
const installation = values.profile === undefined
|
|
@@ -125,15 +142,6 @@ async function main() {
|
|
|
125
142
|
const installationLease = installation === null
|
|
126
143
|
? null
|
|
127
144
|
: await acquireDaemonInstallationLease(installation.npmPrefix);
|
|
128
|
-
const storageRuntime = values.profile === undefined
|
|
129
|
-
? null
|
|
130
|
-
: await loadStorageRuntime({
|
|
131
|
-
profile: values.profile,
|
|
132
|
-
serverUrl: config.serverUrl,
|
|
133
|
-
userHome: homedir(),
|
|
134
|
-
platform: process.platform,
|
|
135
|
-
environment: process.env,
|
|
136
|
-
});
|
|
137
145
|
const migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
|
|
138
146
|
? { ready: false, reason: "not_legacy_source" }
|
|
139
147
|
: await probeStorageMigrationRuntime({
|
|
@@ -188,7 +196,7 @@ async function main() {
|
|
|
188
196
|
}
|
|
189
197
|
: undefined;
|
|
190
198
|
await runWithDaemonInstallationLease(installationLease, async () => {
|
|
191
|
-
const service = serve(
|
|
199
|
+
const service = serve(runtimeConfig, {
|
|
192
200
|
...(values.profile === undefined ? {} : { profileName: values.profile }),
|
|
193
201
|
...(storageMigration === undefined ? {} : { storageMigration }),
|
|
194
202
|
});
|
|
@@ -209,9 +217,9 @@ async function main() {
|
|
|
209
217
|
gitSha: provenance.gitSha,
|
|
210
218
|
artifactDigest: provenance.artifactDigest,
|
|
211
219
|
...(values.profile === undefined ? {} : { profileName: values.profile }),
|
|
212
|
-
agentsRoot:
|
|
220
|
+
agentsRoot: runtimeConfig.agentsRoot,
|
|
213
221
|
}); // 一次性 run 模式也上报 SLS(runner 里的埋点生效)
|
|
214
|
-
const result = await runAgent(
|
|
222
|
+
const result = await runAgent(runtimeConfig, {
|
|
215
223
|
handle: values.agent,
|
|
216
224
|
channelId: values.channel,
|
|
217
225
|
...(values.wake ? { wake: values.wake } : {}),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { posix, win32 } from "node:path";
|
|
2
|
+
/** Execute POSIX npm through its owning Node binary so service PATH is irrelevant. */
|
|
3
|
+
export function npmInvocation(platform, nodePath = process.execPath) {
|
|
4
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
5
|
+
const npmPath = paths.join(paths.dirname(nodePath), platform === "win32" ? "npm.cmd" : "npm");
|
|
6
|
+
return platform === "win32"
|
|
7
|
+
? { command: npmPath, argsPrefix: [] }
|
|
8
|
+
: { command: nodePath, argsPrefix: [npmPath] };
|
|
9
|
+
}
|
|
@@ -111,8 +111,8 @@ async function ensureWorktreeExclude(mainRoot) {
|
|
|
111
111
|
const suffix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
112
112
|
await writeFile(excludePath, current + suffix + ".worktrees\n", "utf8");
|
|
113
113
|
}
|
|
114
|
-
async function ensureMainClone(git, repo, repositoryKey, reposRoot) {
|
|
115
|
-
const mainRoot = resolve(reposRoot,
|
|
114
|
+
async function ensureMainClone(git, repo, repositoryKey, directoryName, reposRoot) {
|
|
115
|
+
const mainRoot = resolve(reposRoot, directoryName);
|
|
116
116
|
if (!isWithin(reposRoot, mainRoot) || await existingAncestorIsSymlink(reposRoot, mainRoot)) {
|
|
117
117
|
throw new ProjectDependencyError("project_dependency_checkout_conflict", "repository path is unsafe", repositoryKey);
|
|
118
118
|
}
|
|
@@ -123,7 +123,7 @@ async function ensureMainClone(git, repo, repositoryKey, reposRoot) {
|
|
|
123
123
|
if (info === null) {
|
|
124
124
|
await mkdir(dirname(mainRoot), { recursive: true });
|
|
125
125
|
try {
|
|
126
|
-
await git.run(["clone",
|
|
126
|
+
await git.run(["clone", repo, mainRoot]);
|
|
127
127
|
}
|
|
128
128
|
catch {
|
|
129
129
|
await rm(mainRoot, { recursive: true, force: true }).catch(() => { });
|
|
@@ -243,7 +243,7 @@ export function createProjectDependencyProvisioner(options = {}) {
|
|
|
243
243
|
for (const { identity, projects } of grouped.values()) {
|
|
244
244
|
try {
|
|
245
245
|
await withProjectDependencyLock(lockRoot, identity.repositoryKey, async () => {
|
|
246
|
-
const mainRoot = await ensureMainClone(git, projects[0].repo, identity.repositoryKey, input.reposRoot);
|
|
246
|
+
const mainRoot = await ensureMainClone(git, projects[0].repo, identity.repositoryKey, identity.directoryName, input.reposRoot);
|
|
247
247
|
const seenVersions = new Set();
|
|
248
248
|
for (const project of projects) {
|
|
249
249
|
const branchSource = project.branch === undefined ? "remote-default" : "manifest";
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { AbilityRepositoryUrlSchema } from "./agent-ability/types.js";
|
|
3
3
|
const SCP_REPOSITORY = /^git@([^:]+):(.+)$/u;
|
|
4
4
|
const COMMIT = /^[a-f0-9]{40}$/u;
|
|
5
|
+
const DIRECTORY_HASH_LENGTH = 12;
|
|
5
6
|
function normalizePath(value) {
|
|
6
7
|
const path = value.replace(/^\/+|\/+$/gu, "").replace(/\.git$/iu, "");
|
|
7
8
|
if (!path || path.split("/").some((part) => part.length === 0 || part === "." || part === ".." || part.includes("\\"))) {
|
|
@@ -31,6 +32,7 @@ export function normalizeRepositoryIdentity(repository) {
|
|
|
31
32
|
canonical,
|
|
32
33
|
repositoryKey: `repo-${hash}`,
|
|
33
34
|
displayName,
|
|
35
|
+
directoryName: `${displayName}--${hash.slice(0, DIRECTORY_HASH_LENGTH)}`,
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
export function worktreeDirectoryName(branch, resolvedCommit) {
|
package/dist/runner.js
CHANGED
|
@@ -84,6 +84,8 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
|
|
|
84
84
|
token: credential.token,
|
|
85
85
|
agentId: credential.agentId,
|
|
86
86
|
agentsRoot: config.agentsRoot,
|
|
87
|
+
...(config.reposRoot === undefined ? {} : { reposRoot: config.reposRoot }),
|
|
88
|
+
...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
|
|
87
89
|
cliPath: config.cliPath,
|
|
88
90
|
providerConfig,
|
|
89
91
|
codexHomeMode: config.codexHomeMode,
|
package/dist/storage-layout.js
CHANGED
|
@@ -121,6 +121,7 @@ export function storageLayout(input) {
|
|
|
121
121
|
agentItemsRoot: paths.join(workspaceRoot, "agent-items"),
|
|
122
122
|
daemonRoot: paths.join(workspaceRoot, "daemon"),
|
|
123
123
|
repoRoot: paths.join(workspaceRoot, "repos"),
|
|
124
|
+
qaReportRoot: paths.join(workspaceRoot, "qa-report-server-v2"),
|
|
124
125
|
suiteToolsRoot: paths.join(workspaceRoot, "suite-tools"),
|
|
125
126
|
migrationsRoot: paths.join(persistentRoot, "migrations"),
|
|
126
127
|
runtimeRoot,
|
|
@@ -132,7 +133,7 @@ export function agentStorageRoot(layout, agentId) {
|
|
|
132
133
|
}
|
|
133
134
|
const COMPARABLE_FIELDS = [
|
|
134
135
|
"layoutVersion", "environment", "workspaceId", "persistentRoot", "environmentRoot", "workspaceRoot",
|
|
135
|
-
"agentItemsRoot", "daemonRoot", "repoRoot", "suiteToolsRoot", "migrationsRoot", "runtimeRoot",
|
|
136
|
+
"agentItemsRoot", "daemonRoot", "repoRoot", "qaReportRoot", "suiteToolsRoot", "migrationsRoot", "runtimeRoot",
|
|
136
137
|
];
|
|
137
138
|
export function compareStorageLayout(current, expected) {
|
|
138
139
|
if (current === null || current.layoutVersion !== STORAGE_LAYOUT_VERSION)
|