@tuned-tensor/cli 0.26.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/dist/{chunk-VQWJ2XKR.js → chunk-7CO5DBCY.js} +448 -123
- package/dist/chunk-7CO5DBCY.js.map +1 -0
- package/dist/{chunk-2AIP63QO.js → chunk-FSVTMX4L.js} +13 -7
- package/dist/chunk-FSVTMX4L.js.map +1 -0
- package/dist/index.js +482 -248
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +22 -9
- package/dist/local-runtime.js.map +1 -1
- package/dist/{prefetch-B6I7B4LR.js → prefetch-LBIOO7ZO.js} +2 -2
- package/docs/local-runtime/decision.md +2 -1
- package/docs/local-runtime/hosting.md +121 -0
- package/docs/windows.md +151 -0
- package/package.json +3 -1
- package/training/serving/uv.lock +3 -3
- package/dist/chunk-2AIP63QO.js.map +0 -1
- package/dist/chunk-VQWJ2XKR.js.map +0 -1
- /package/dist/{prefetch-B6I7B4LR.js.map → prefetch-LBIOO7ZO.js.map} +0 -0
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
writeFileAtomic,
|
|
64
64
|
writeJson,
|
|
65
65
|
writeJsonAtomic
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-FSVTMX4L.js";
|
|
67
67
|
|
|
68
68
|
// src/local-runtime/dataset.ts
|
|
69
69
|
import { readFile } from "fs/promises";
|
|
@@ -868,16 +868,259 @@ function assertFoundationSettingsMatch(hp, plan2) {
|
|
|
868
868
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
|
|
869
869
|
import { constants as constants4 } from "fs";
|
|
870
870
|
import { lstat as lstat3, mkdir as mkdir3, open as open4, rename as rename2, rm, rmdir, writeFile as writeFile3 } from "fs/promises";
|
|
871
|
-
import { dirname as
|
|
871
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join5, relative as relative4, resolve as resolve2, sep as sep4 } from "path";
|
|
872
872
|
import { z } from "zod";
|
|
873
873
|
|
|
874
874
|
// src/local-spec-workspace.ts
|
|
875
875
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
876
876
|
import { constants } from "fs";
|
|
877
877
|
import { lstat, open, realpath, writeFile as writeFile2 } from "fs/promises";
|
|
878
|
-
import { join } from "path";
|
|
878
|
+
import { join as join2 } from "path";
|
|
879
879
|
import { Type } from "typebox";
|
|
880
880
|
import { Value } from "typebox/value";
|
|
881
|
+
|
|
882
|
+
// src/windows-workspace.ts
|
|
883
|
+
import { spawn } from "child_process";
|
|
884
|
+
import { createInterface } from "readline";
|
|
885
|
+
import { join } from "path";
|
|
886
|
+
var HELPER = String.raw`
|
|
887
|
+
$ErrorActionPreference = 'Stop'
|
|
888
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
889
|
+
[Console]::InputEncoding = New-Object Text.UTF8Encoding($false)
|
|
890
|
+
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
|
|
891
|
+
Add-Type -TypeDefinition @'
|
|
892
|
+
using System;
|
|
893
|
+
using System.ComponentModel;
|
|
894
|
+
using System.IO;
|
|
895
|
+
using System.Runtime.InteropServices;
|
|
896
|
+
using System.Security.AccessControl;
|
|
897
|
+
using System.Security.Principal;
|
|
898
|
+
using System.Text;
|
|
899
|
+
using Microsoft.Win32.SafeHandles;
|
|
900
|
+
public static class TTWorkspace {
|
|
901
|
+
[StructLayout(LayoutKind.Sequential)] struct Info {
|
|
902
|
+
public uint Attributes, CreationLow, CreationHigh, AccessLow, AccessHigh,
|
|
903
|
+
WriteLow, WriteHigh, Volume, SizeHigh, SizeLow, Links, IndexHigh, IndexLow;
|
|
904
|
+
}
|
|
905
|
+
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
|
906
|
+
static extern SafeFileHandle CreateFileW(string path, uint access, uint share,
|
|
907
|
+
IntPtr security, uint disposition, uint flags, IntPtr template);
|
|
908
|
+
[DllImport("kernel32.dll", SetLastError=true)]
|
|
909
|
+
static extern bool GetFileInformationByHandle(SafeFileHandle handle, out Info info);
|
|
910
|
+
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
|
911
|
+
static extern bool MoveFileExW(string source, string target, uint flags);
|
|
912
|
+
static string NativePath(string path) {
|
|
913
|
+
path = Path.GetFullPath(path);
|
|
914
|
+
return path.StartsWith(@"\\") ? @"\\?\UNC\" + path.Substring(2) : @"\\?\" + path;
|
|
915
|
+
}
|
|
916
|
+
static SafeFileHandle Open(string path, bool directory) {
|
|
917
|
+
// No FILE_SHARE_DELETE: the directory cannot be renamed or replaced.
|
|
918
|
+
var handle = CreateFileW(NativePath(path), 0x80000000u,
|
|
919
|
+
directory ? 3u : 1u, IntPtr.Zero, 3, 0x02200000, IntPtr.Zero);
|
|
920
|
+
if (handle.IsInvalid) { int error = Marshal.GetLastWin32Error(); handle.Dispose(); throw new Win32Exception(error); }
|
|
921
|
+
Info info;
|
|
922
|
+
if (!GetFileInformationByHandle(handle, out info)) {
|
|
923
|
+
int error = Marshal.GetLastWin32Error(); handle.Dispose(); throw new Win32Exception(error);
|
|
924
|
+
}
|
|
925
|
+
if ((info.Attributes & 0x400) != 0 || (((info.Attributes & 0x10) != 0) != directory)) {
|
|
926
|
+
handle.Dispose(); throw new IOException("Workspace entries must be real directories or regular files, not symlinks/reparse points.");
|
|
927
|
+
}
|
|
928
|
+
return handle;
|
|
929
|
+
}
|
|
930
|
+
public static SafeFileHandle DirectoryHandle(string path) { return Open(path, true); }
|
|
931
|
+
public static string Child(string directory, string name) {
|
|
932
|
+
if (String.IsNullOrEmpty(name) || name == "." || name == ".." || name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
|
933
|
+
throw new IOException("Invalid workspace entry name.");
|
|
934
|
+
return Path.Combine(directory, name);
|
|
935
|
+
}
|
|
936
|
+
public static string Read(string path, int limit) {
|
|
937
|
+
using (var handle = Open(path, false))
|
|
938
|
+
using (var stream = new FileStream(handle, FileAccess.Read)) {
|
|
939
|
+
if (stream.Length > limit) throw new IOException("Spec/revision exceeds the byte limit.");
|
|
940
|
+
using (var bytes = new MemoryStream()) {
|
|
941
|
+
var buffer = new byte[16384]; int count;
|
|
942
|
+
while ((count = stream.Read(buffer, 0, buffer.Length)) > 0) {
|
|
943
|
+
if (bytes.Length + count > limit) throw new IOException("Spec/revision exceeds the byte limit.");
|
|
944
|
+
bytes.Write(buffer, 0, count);
|
|
945
|
+
}
|
|
946
|
+
return new UTF8Encoding(false, true).GetString(bytes.ToArray());
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
public static void WriteNew(string path, string source) {
|
|
951
|
+
byte[] bytes = new UTF8Encoding(false).GetBytes(source);
|
|
952
|
+
var security = new FileSecurity();
|
|
953
|
+
security.SetAccessRuleProtection(true, false);
|
|
954
|
+
security.AddAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().User, FileSystemRights.FullControl, AccessControlType.Allow));
|
|
955
|
+
using (var stream = new FileStream(NativePath(path), FileMode.CreateNew, FileSystemRights.Write, FileShare.Read, 4096, FileOptions.None, security)) {
|
|
956
|
+
stream.Write(bytes, 0, bytes.Length); stream.Flush(true);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
public static FileStream LockFile(string path) {
|
|
960
|
+
return new FileStream(NativePath(path), FileMode.CreateNew, FileAccess.ReadWrite,
|
|
961
|
+
FileShare.None, 1, FileOptions.DeleteOnClose);
|
|
962
|
+
}
|
|
963
|
+
public static void Atomic(string directory, string name, string source) {
|
|
964
|
+
string temporary = Child(directory, ".tt-" + Guid.NewGuid().ToString() + ".tmp");
|
|
965
|
+
try {
|
|
966
|
+
WriteNew(temporary, source);
|
|
967
|
+
if (!MoveFileExW(NativePath(temporary), NativePath(Child(directory, name)), 9))
|
|
968
|
+
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
969
|
+
} finally { File.Delete(NativePath(temporary)); }
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
'@
|
|
973
|
+
$handles = New-Object 'Collections.Generic.List[System.IDisposable]'
|
|
974
|
+
$directories = New-Object 'Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
|
|
975
|
+
function Lock-Directory([string]$path) {
|
|
976
|
+
$path = [IO.Path]::GetFullPath($path)
|
|
977
|
+
$current = [IO.Path]::GetPathRoot($path)
|
|
978
|
+
$parts = @('') + @($path.Substring($current.Length).Split('\') | Where-Object { $_ })
|
|
979
|
+
foreach ($part in $parts) {
|
|
980
|
+
if ($part) { $current = [IO.Path]::Combine($current, $part) }
|
|
981
|
+
if (-not $directories.Contains($current)) {
|
|
982
|
+
$handle = [TTWorkspace]::DirectoryHandle($current)
|
|
983
|
+
$handles.Add($handle)
|
|
984
|
+
[void]$directories.Add($current)
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
while ($null -ne ($line = [Console]::ReadLine())) {
|
|
990
|
+
try {
|
|
991
|
+
$request = $line | ConvertFrom-Json
|
|
992
|
+
if ($request.op -eq 'directory') {
|
|
993
|
+
Lock-Directory $request.directory
|
|
994
|
+
$result = $null
|
|
995
|
+
} else {
|
|
996
|
+
if (-not $directories.Contains([IO.Path]::GetFullPath($request.directory))) { throw 'Directory is not pinned.' }
|
|
997
|
+
$path = [TTWorkspace]::Child($request.directory, $request.name)
|
|
998
|
+
switch ($request.op) {
|
|
999
|
+
'read' { $result = [TTWorkspace]::Read($path, $request.limit) }
|
|
1000
|
+
'create' { [TTWorkspace]::WriteNew($path, $request.source); $result = $null }
|
|
1001
|
+
'atomic' { [TTWorkspace]::Atomic($request.directory, $request.name, $request.source); $result = $null }
|
|
1002
|
+
'lock' { $handles.Add([TTWorkspace]::LockFile($path)); $result = $null }
|
|
1003
|
+
'child' {
|
|
1004
|
+
[void][IO.Directory]::CreateDirectory($path)
|
|
1005
|
+
Lock-Directory $path
|
|
1006
|
+
$result = $path
|
|
1007
|
+
}
|
|
1008
|
+
default { throw 'Unknown workspace operation.' }
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
[Console]::WriteLine((@{ ok = $true; result = $result } | ConvertTo-Json -Compress -Depth 3))
|
|
1012
|
+
} catch {
|
|
1013
|
+
$native = $_.Exception.GetBaseException()
|
|
1014
|
+
$number = if ($native -is [ComponentModel.Win32Exception]) { $native.NativeErrorCode } else { $native.HResult -band 65535 }
|
|
1015
|
+
$code = switch ($number) { 2 { 'ENOENT' } 3 { 'ENOENT' } 80 { 'EEXIST' } 183 { 'EEXIST' } default { 'EIO' } }
|
|
1016
|
+
[Console]::WriteLine((@{ ok = $false; error = $native.Message; code = $code } | ConvertTo-Json -Compress))
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
} finally {
|
|
1020
|
+
for ($i = $handles.Count - 1; $i -ge 0; $i--) { $handles[$i].Dispose() }
|
|
1021
|
+
}
|
|
1022
|
+
`;
|
|
1023
|
+
var WindowsWorkspace = class _WindowsWorkspace {
|
|
1024
|
+
child;
|
|
1025
|
+
exited;
|
|
1026
|
+
pending;
|
|
1027
|
+
failure;
|
|
1028
|
+
stderr = "";
|
|
1029
|
+
constructor() {
|
|
1030
|
+
const executable = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
1031
|
+
this.child = spawn(executable, [
|
|
1032
|
+
"-NoLogo",
|
|
1033
|
+
"-NoProfile",
|
|
1034
|
+
"-NonInteractive",
|
|
1035
|
+
"-OutputFormat",
|
|
1036
|
+
"Text",
|
|
1037
|
+
"-ExecutionPolicy",
|
|
1038
|
+
"Bypass",
|
|
1039
|
+
"-EncodedCommand",
|
|
1040
|
+
Buffer.from(HELPER, "utf16le").toString("base64")
|
|
1041
|
+
], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
1042
|
+
const fail = (error) => {
|
|
1043
|
+
this.failure = error;
|
|
1044
|
+
this.pending?.reject(error);
|
|
1045
|
+
this.pending = void 0;
|
|
1046
|
+
};
|
|
1047
|
+
this.child.on("error", fail);
|
|
1048
|
+
this.child.stdin.on("error", fail);
|
|
1049
|
+
this.child.stderr.on("data", (data) => {
|
|
1050
|
+
this.stderr = (this.stderr + String(data)).slice(-4e3);
|
|
1051
|
+
});
|
|
1052
|
+
createInterface({ input: this.child.stdout }).on("line", (line) => {
|
|
1053
|
+
const pending = this.pending;
|
|
1054
|
+
if (!pending) return;
|
|
1055
|
+
this.pending = void 0;
|
|
1056
|
+
try {
|
|
1057
|
+
const response = JSON.parse(line);
|
|
1058
|
+
if (response.ok) pending.resolve(response.result);
|
|
1059
|
+
else pending.reject(Object.assign(new Error(response.error ?? "Windows workspace operation failed."), { code: response.code }));
|
|
1060
|
+
} catch {
|
|
1061
|
+
pending.reject(new Error("Invalid Windows workspace helper response."));
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
this.exited = new Promise((resolve12) => this.child.once("close", () => {
|
|
1065
|
+
fail(new Error(`Windows workspace helper stopped. ${this.stderr}`));
|
|
1066
|
+
resolve12();
|
|
1067
|
+
}));
|
|
1068
|
+
}
|
|
1069
|
+
static async open(directory) {
|
|
1070
|
+
const workspace = new _WindowsWorkspace();
|
|
1071
|
+
try {
|
|
1072
|
+
await workspace.request({ op: "directory", directory });
|
|
1073
|
+
return workspace;
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
await workspace.close();
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async request(command) {
|
|
1080
|
+
if (this.failure) throw this.failure;
|
|
1081
|
+
if (this.pending) throw new Error("Concurrent operations on one Windows workspace transaction are not supported.");
|
|
1082
|
+
return await new Promise((resolve12, reject) => {
|
|
1083
|
+
const timer = setTimeout(() => {
|
|
1084
|
+
this.child.kill();
|
|
1085
|
+
reject(new Error("Windows workspace operation timed out."));
|
|
1086
|
+
}, 3e4);
|
|
1087
|
+
this.pending = { resolve: (value) => {
|
|
1088
|
+
clearTimeout(timer);
|
|
1089
|
+
resolve12(value);
|
|
1090
|
+
}, reject: (error) => {
|
|
1091
|
+
clearTimeout(timer);
|
|
1092
|
+
reject(error);
|
|
1093
|
+
} };
|
|
1094
|
+
this.child.stdin.write(JSON.stringify(command) + "\n");
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
async read(directory, name, limit) {
|
|
1098
|
+
return await this.request({ op: "read", directory, name, limit });
|
|
1099
|
+
}
|
|
1100
|
+
async create(directory, name, source) {
|
|
1101
|
+
await this.request({ op: "create", directory, name, source });
|
|
1102
|
+
}
|
|
1103
|
+
async atomic(directory, name, source) {
|
|
1104
|
+
await this.request({ op: "atomic", directory, name, source });
|
|
1105
|
+
}
|
|
1106
|
+
async childDirectory(directory, name) {
|
|
1107
|
+
return await this.request({ op: "child", directory, name });
|
|
1108
|
+
}
|
|
1109
|
+
async lock(directory, name) {
|
|
1110
|
+
await this.request({ op: "lock", directory, name });
|
|
1111
|
+
}
|
|
1112
|
+
async close() {
|
|
1113
|
+
this.child.stdin.end();
|
|
1114
|
+
const timer = setTimeout(() => this.child.kill(), 3e3);
|
|
1115
|
+
try {
|
|
1116
|
+
await this.exited;
|
|
1117
|
+
} finally {
|
|
1118
|
+
clearTimeout(timer);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
// src/local-spec-workspace.ts
|
|
881
1124
|
var LocalBaseModel = Type.String({
|
|
882
1125
|
minLength: 3,
|
|
883
1126
|
maxLength: 193,
|
|
@@ -952,7 +1195,7 @@ async function prepareLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFin
|
|
|
952
1195
|
}
|
|
953
1196
|
validateInputs(spec);
|
|
954
1197
|
try {
|
|
955
|
-
await lstat(
|
|
1198
|
+
await lstat(join2(canonicalRoot, SPEC_FILE));
|
|
956
1199
|
} catch (error) {
|
|
957
1200
|
if (errorCode(error) === "ENOENT") {
|
|
958
1201
|
return { specPath: SPEC_DISPLAY_PATH, workspaceFingerprint };
|
|
@@ -973,19 +1216,22 @@ async function createLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFing
|
|
|
973
1216
|
const detail = error instanceof Error ? error.message : String(error);
|
|
974
1217
|
throw new LocalSpecMutationError(detail, "not_applied", error);
|
|
975
1218
|
}
|
|
976
|
-
if (process.platform !== "linux") {
|
|
1219
|
+
if (process.platform !== "linux" && process.platform !== "win32") {
|
|
977
1220
|
throw new LocalSpecMutationError(
|
|
978
1221
|
"Secure local spec creation currently requires Linux filesystem handle support.",
|
|
979
1222
|
"not_applied"
|
|
980
1223
|
);
|
|
981
1224
|
}
|
|
982
1225
|
let workspaceHandle;
|
|
1226
|
+
let windows;
|
|
983
1227
|
try {
|
|
984
|
-
|
|
1228
|
+
if (process.platform === "win32") {
|
|
1229
|
+
windows = await WindowsWorkspace.open(canonicalRoot);
|
|
1230
|
+
} else workspaceHandle = await open(
|
|
985
1231
|
canonicalRoot,
|
|
986
1232
|
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW
|
|
987
1233
|
);
|
|
988
|
-
if (fingerprintWorkspaceInfo(await workspaceHandle.stat()) !== expectedWorkspaceFingerprint) {
|
|
1234
|
+
if ((windows ? await fingerprintWorkspace(canonicalRoot) : fingerprintWorkspaceInfo(await workspaceHandle.stat())) !== expectedWorkspaceFingerprint) {
|
|
989
1235
|
throw new LocalSpecMutationError(
|
|
990
1236
|
"The local workspace changed after this action was prepared; return to the original workspace and prepare it again.",
|
|
991
1237
|
"not_applied"
|
|
@@ -998,15 +1244,18 @@ async function createLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFing
|
|
|
998
1244
|
} catch {
|
|
999
1245
|
}
|
|
1000
1246
|
}
|
|
1247
|
+
await windows?.close();
|
|
1001
1248
|
if (error instanceof LocalSpecMutationError) throw error;
|
|
1002
1249
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1003
1250
|
throw new LocalSpecMutationError(detail, "not_applied", error);
|
|
1004
1251
|
}
|
|
1005
1252
|
const operations = { ...NODE_FILE_OPERATIONS, ...operationOverrides };
|
|
1006
|
-
const stableSpecPath =
|
|
1253
|
+
const stableSpecPath = windows ? join2(canonicalRoot, SPEC_FILE) : join2(`/proc/self/fd/${workspaceHandle.fd}`, SPEC_FILE);
|
|
1007
1254
|
try {
|
|
1008
|
-
|
|
1009
|
-
|
|
1255
|
+
const source = `${JSON.stringify({ id: randomUUID2(), ...spec }, null, 2)}
|
|
1256
|
+
`;
|
|
1257
|
+
if (windows && !operationOverrides.writeFile) await windows.create(canonicalRoot, SPEC_FILE, source);
|
|
1258
|
+
else await operations.writeFile(stableSpecPath, source, {
|
|
1010
1259
|
encoding: "utf8",
|
|
1011
1260
|
flag: "wx",
|
|
1012
1261
|
mode: 384
|
|
@@ -1017,9 +1266,10 @@ async function createLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFing
|
|
|
1017
1266
|
}
|
|
1018
1267
|
} catch (error) {
|
|
1019
1268
|
try {
|
|
1020
|
-
await workspaceHandle
|
|
1269
|
+
await workspaceHandle?.close();
|
|
1021
1270
|
} catch {
|
|
1022
1271
|
}
|
|
1272
|
+
await windows?.close();
|
|
1023
1273
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1024
1274
|
if (errorCode(error) === "EEXIST") {
|
|
1025
1275
|
throw new LocalSpecMutationError(`Refusing to overwrite existing ${SPEC_DISPLAY_PATH}.`, "not_applied", error);
|
|
@@ -1031,9 +1281,10 @@ async function createLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFing
|
|
|
1031
1281
|
);
|
|
1032
1282
|
}
|
|
1033
1283
|
try {
|
|
1034
|
-
await workspaceHandle
|
|
1284
|
+
await workspaceHandle?.close();
|
|
1035
1285
|
} catch {
|
|
1036
1286
|
}
|
|
1287
|
+
await windows?.close();
|
|
1037
1288
|
return { created: true, path: prepared.specPath };
|
|
1038
1289
|
}
|
|
1039
1290
|
|
|
@@ -1041,7 +1292,7 @@ async function createLocalSpecProject(workspaceRoot, spec, expectedWorkspaceFing
|
|
|
1041
1292
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1042
1293
|
import { constants as constants2 } from "fs";
|
|
1043
1294
|
import { lstat as lstat2, mkdir as mkdir2, open as open2, readdir, rename, unlink } from "fs/promises";
|
|
1044
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1295
|
+
import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, sep as sep2 } from "path";
|
|
1045
1296
|
var MAX_BYTES = 2e5;
|
|
1046
1297
|
var EDIT_KEYS = /* @__PURE__ */ new Set([
|
|
1047
1298
|
"name",
|
|
@@ -1074,6 +1325,14 @@ function validateDocument(document, path) {
|
|
|
1074
1325
|
}
|
|
1075
1326
|
}
|
|
1076
1327
|
async function readBounded(path, limit = MAX_BYTES) {
|
|
1328
|
+
if (process.platform === "win32") {
|
|
1329
|
+
const windows = await WindowsWorkspace.open(dirname2(path));
|
|
1330
|
+
try {
|
|
1331
|
+
return await windows.read(dirname2(path), basename(path), limit);
|
|
1332
|
+
} finally {
|
|
1333
|
+
await windows.close();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1077
1336
|
const handle = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
1078
1337
|
try {
|
|
1079
1338
|
const info = await handle.stat();
|
|
@@ -1093,19 +1352,19 @@ async function readBounded(path, limit = MAX_BYTES) {
|
|
|
1093
1352
|
}
|
|
1094
1353
|
async function specLocation(workspaceRoot, specPath) {
|
|
1095
1354
|
const root = await canonicalWorkspace(workspaceRoot);
|
|
1096
|
-
const parts = specPath.replace(
|
|
1355
|
+
const parts = specPath.replace(/^\.[\\/]/, "").split(/[\\/]/);
|
|
1097
1356
|
if (isAbsolute2(specPath) || parts.some((part) => !part || part === "." || part === "..") || parts.at(-1) !== "tunedtensor.json") {
|
|
1098
1357
|
throw new Error("spec_path must name tunedtensor.json inside the current workspace (no traversal or symlinks).");
|
|
1099
1358
|
}
|
|
1100
1359
|
let parent = root;
|
|
1101
1360
|
for (const part of parts.slice(0, -1)) {
|
|
1102
|
-
parent =
|
|
1361
|
+
parent = join3(parent, part);
|
|
1103
1362
|
const info = await lstat2(parent);
|
|
1104
1363
|
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("Spec directories must be real directories, not symlinks.");
|
|
1105
1364
|
}
|
|
1106
1365
|
return {
|
|
1107
1366
|
root,
|
|
1108
|
-
path:
|
|
1367
|
+
path: join3(parent, "tunedtensor.json"),
|
|
1109
1368
|
parent,
|
|
1110
1369
|
displayPath: `./${parts.join("/")}`,
|
|
1111
1370
|
workspaceFingerprint: await fingerprintWorkspace(root),
|
|
@@ -1119,6 +1378,15 @@ async function assertCurrentLocation(location2) {
|
|
|
1119
1378
|
}
|
|
1120
1379
|
}
|
|
1121
1380
|
async function readSpecSource(location2) {
|
|
1381
|
+
if (process.platform === "win32") {
|
|
1382
|
+
const windows = await WindowsWorkspace.open(location2.parent);
|
|
1383
|
+
try {
|
|
1384
|
+
await assertCurrentLocation(location2);
|
|
1385
|
+
return await windows.read(location2.parent, "tunedtensor.json", MAX_BYTES);
|
|
1386
|
+
} finally {
|
|
1387
|
+
await windows.close();
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1122
1390
|
if (process.platform !== "linux") return await readBounded(location2.path);
|
|
1123
1391
|
const handles = [];
|
|
1124
1392
|
try {
|
|
@@ -1269,7 +1537,7 @@ async function privateChild(parent, name) {
|
|
|
1269
1537
|
}
|
|
1270
1538
|
async function atomicText(parent, name, source) {
|
|
1271
1539
|
const base = `/proc/self/fd/${parent.fd}`;
|
|
1272
|
-
const temporary =
|
|
1540
|
+
const temporary = join3(base, `.tt-${randomUUID3()}.tmp`);
|
|
1273
1541
|
try {
|
|
1274
1542
|
const file = await open2(temporary, "wx", 384);
|
|
1275
1543
|
try {
|
|
@@ -1278,7 +1546,7 @@ async function atomicText(parent, name, source) {
|
|
|
1278
1546
|
} finally {
|
|
1279
1547
|
await file.close();
|
|
1280
1548
|
}
|
|
1281
|
-
await rename(temporary,
|
|
1549
|
+
await rename(temporary, join3(base, name));
|
|
1282
1550
|
await parent.sync();
|
|
1283
1551
|
} finally {
|
|
1284
1552
|
await unlink(temporary).catch(() => {
|
|
@@ -1286,6 +1554,7 @@ async function atomicText(parent, name, source) {
|
|
|
1286
1554
|
}
|
|
1287
1555
|
}
|
|
1288
1556
|
async function applySpecUpdate(workspaceRoot, update) {
|
|
1557
|
+
if (process.platform === "win32") return await applyWindowsSpecUpdate(workspaceRoot, update);
|
|
1289
1558
|
let committing = false;
|
|
1290
1559
|
let lock;
|
|
1291
1560
|
const handles = [];
|
|
@@ -1338,11 +1607,48 @@ async function applySpecUpdate(workspaceRoot, update) {
|
|
|
1338
1607
|
for (const handle of handles.reverse()) await handle.close();
|
|
1339
1608
|
}
|
|
1340
1609
|
}
|
|
1610
|
+
async function applyWindowsSpecUpdate(workspaceRoot, update) {
|
|
1611
|
+
let windows;
|
|
1612
|
+
let committing = false;
|
|
1613
|
+
try {
|
|
1614
|
+
const prepared = await validateSpecUpdate(workspaceRoot, update);
|
|
1615
|
+
windows = await WindowsWorkspace.open(prepared.current.parent);
|
|
1616
|
+
await assertCurrentLocation(prepared.current);
|
|
1617
|
+
const parent = prepared.current.parent;
|
|
1618
|
+
await windows.lock(parent, ".tunedtensor-spec.lock");
|
|
1619
|
+
const before = await windows.read(parent, "tunedtensor.json", MAX_BYTES);
|
|
1620
|
+
if (specHash(before) !== update.expected_sha256) throw new Error("The behavior spec changed; inspect it and prepare the edit again.");
|
|
1621
|
+
const state = await windows.childDirectory(parent, ".tuned-tensor");
|
|
1622
|
+
const history = await windows.childDirectory(state, "spec-history");
|
|
1623
|
+
const id = `${Date.now()}-${randomUUID3()}`;
|
|
1624
|
+
const revision = {
|
|
1625
|
+
id,
|
|
1626
|
+
status: "prepared",
|
|
1627
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1628
|
+
before_sha256: update.expected_sha256,
|
|
1629
|
+
after_sha256: update.after_sha256,
|
|
1630
|
+
before: prepared.current.document,
|
|
1631
|
+
after: prepared.next
|
|
1632
|
+
};
|
|
1633
|
+
await windows.atomic(history, `${id}.json`, JSON.stringify(revision));
|
|
1634
|
+
if (specHash(await windows.read(parent, "tunedtensor.json", MAX_BYTES)) !== update.expected_sha256) {
|
|
1635
|
+
throw new Error("The behavior spec changed before commit; prepare the edit again.");
|
|
1636
|
+
}
|
|
1637
|
+
committing = true;
|
|
1638
|
+
await windows.atomic(parent, "tunedtensor.json", prepared.source);
|
|
1639
|
+
await windows.atomic(history, `${id}.json`, JSON.stringify({ ...revision, status: "applied" }));
|
|
1640
|
+
return { updated: true, path: update.spec_path, revision: id, sha256: revision.after_sha256, diff: prepared.diff };
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
throw new LocalSpecMutationError(error.message, committing ? "unknown" : "not_applied", error);
|
|
1643
|
+
} finally {
|
|
1644
|
+
await windows?.close();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1341
1647
|
async function readSpecHistory(workspaceRoot, specPath = "tunedtensor.json") {
|
|
1342
1648
|
const location2 = await specLocation(workspaceRoot, specPath);
|
|
1343
1649
|
let path = location2.parent;
|
|
1344
1650
|
for (const part of [".tuned-tensor", "spec-history"]) {
|
|
1345
|
-
path =
|
|
1651
|
+
path = join3(path, part);
|
|
1346
1652
|
try {
|
|
1347
1653
|
const info = await lstat2(path);
|
|
1348
1654
|
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
@@ -1354,13 +1660,13 @@ async function readSpecHistory(workspaceRoot, specPath = "tunedtensor.json") {
|
|
|
1354
1660
|
}
|
|
1355
1661
|
}
|
|
1356
1662
|
const names = (await readdir(path)).filter((name) => /^\d+-[a-f0-9-]+\.json$/.test(name)).sort().reverse().slice(0, 50);
|
|
1357
|
-
return await Promise.all(names.map(async (name) => JSON.parse(await readBounded(
|
|
1663
|
+
return await Promise.all(names.map(async (name) => JSON.parse(await readBounded(join3(path, name), MAX_BYTES * 3))));
|
|
1358
1664
|
}
|
|
1359
1665
|
|
|
1360
1666
|
// src/agent-local-datasets.ts
|
|
1361
1667
|
import { constants as constants3 } from "fs";
|
|
1362
1668
|
import { open as open3, realpath as realpath2, stat as stat2 } from "fs/promises";
|
|
1363
|
-
import { basename, dirname as
|
|
1669
|
+
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute3, join as join4, relative as relative3, sep as sep3 } from "path";
|
|
1364
1670
|
import { parquetMetadataAsync, parquetReadObjects, parquetSchema } from "hyparquet";
|
|
1365
1671
|
import { compressors } from "hyparquet-compressors";
|
|
1366
1672
|
var MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
@@ -1374,12 +1680,12 @@ async function cachedPath(asset, file) {
|
|
|
1374
1680
|
throw new Error("Choose an exact recorded file from list_local_datasets; arbitrary paths are not supported.");
|
|
1375
1681
|
}
|
|
1376
1682
|
const snapshot = asset.snapshot_path;
|
|
1377
|
-
const repoRoot =
|
|
1378
|
-
if (!/^[a-f0-9]{40}$/.test(asset.revision) ||
|
|
1683
|
+
const repoRoot = dirname3(dirname3(snapshot));
|
|
1684
|
+
if (!/^[a-f0-9]{40}$/.test(asset.revision) || basename2(snapshot) !== asset.revision || basename2(dirname3(snapshot)) !== "snapshots" || basename2(repoRoot) !== `datasets--${asset.repo.replaceAll("/", "--")}`) {
|
|
1379
1685
|
throw new Error("The dataset manifest has an invalid cache location.");
|
|
1380
1686
|
}
|
|
1381
1687
|
const root = await realpath2(repoRoot);
|
|
1382
|
-
const target = await realpath2(
|
|
1688
|
+
const target = await realpath2(join4(snapshot, file));
|
|
1383
1689
|
const child = relative3(root, target);
|
|
1384
1690
|
if (isAbsolute3(child) || child.startsWith(`..${sep3}`) || child === ".." || !child) {
|
|
1385
1691
|
throw new Error("The recorded dataset file points outside its cache directory.");
|
|
@@ -1533,7 +1839,7 @@ async function location(workspaceRoot, name, missingLeaf = false) {
|
|
|
1533
1839
|
let current = root;
|
|
1534
1840
|
const parts = child.split(sep4);
|
|
1535
1841
|
for (const [i, part] of parts.entries()) {
|
|
1536
|
-
current =
|
|
1842
|
+
current = join5(current, part);
|
|
1537
1843
|
let info;
|
|
1538
1844
|
try {
|
|
1539
1845
|
info = await lstat3(current);
|
|
@@ -1704,7 +2010,7 @@ async function plan(workspaceRoot, input, signal) {
|
|
|
1704
2010
|
counts,
|
|
1705
2011
|
trainingOnlyRows,
|
|
1706
2012
|
workspace_fingerprint: await fingerprintWorkspace(source.root),
|
|
1707
|
-
parent_fingerprint: await fingerprintWorkspace(
|
|
2013
|
+
parent_fingerprint: await fingerprintWorkspace(dirname4(target.path))
|
|
1708
2014
|
};
|
|
1709
2015
|
}
|
|
1710
2016
|
async function prepareDatasetSplit(workspaceRoot, input, signal) {
|
|
@@ -1737,14 +2043,14 @@ async function validateDatasetSplit(workspaceRoot, input, signal) {
|
|
|
1737
2043
|
}
|
|
1738
2044
|
async function executeDatasetSplit(workspaceRoot, input, signal) {
|
|
1739
2045
|
const p = await validateDatasetSplit(workspaceRoot, input, signal);
|
|
1740
|
-
const stage =
|
|
2046
|
+
const stage = join5(dirname4(p.target.path), `.tt-split-${randomUUID4()}`);
|
|
1741
2047
|
await mkdir3(stage, { mode: 448 });
|
|
1742
2048
|
const files = {};
|
|
1743
2049
|
const handles = /* @__PURE__ */ new Map();
|
|
1744
2050
|
let reserved = false;
|
|
1745
2051
|
try {
|
|
1746
2052
|
for (const split of SPLITS) if (p.counts[split]) {
|
|
1747
|
-
handles.set(split, await open4(
|
|
2053
|
+
handles.set(split, await open4(join5(stage, `${split}.jsonl`), "wx", 384));
|
|
1748
2054
|
files[split] = `${p.options.output}/${split}.jsonl`;
|
|
1749
2055
|
}
|
|
1750
2056
|
const written = { training: 0, validation: 0, test: 0 };
|
|
@@ -1771,13 +2077,15 @@ async function executeDatasetSplit(workspaceRoot, input, signal) {
|
|
|
1771
2077
|
dataset_prebuilt: { ...files, format: "chat_jsonl" },
|
|
1772
2078
|
note: "Source and spec unchanged. Groups containing repeated prompts were kept in training. Inspect this result, then update dataset_prebuilt in your spec separately. Original source split labels were not enforced."
|
|
1773
2079
|
};
|
|
1774
|
-
await writeFile3(
|
|
2080
|
+
await writeFile3(join5(stage, "split-manifest.json"), `${JSON.stringify(report, null, 2)}
|
|
1775
2081
|
`, { flag: "wx" });
|
|
1776
2082
|
signal?.throwIfAborted();
|
|
1777
2083
|
await location(workspaceRoot, p.options.output, true);
|
|
1778
|
-
if (await fingerprintWorkspace(
|
|
1779
|
-
|
|
1780
|
-
|
|
2084
|
+
if (await fingerprintWorkspace(dirname4(p.target.path)) !== p.parent_fingerprint) throw new Error("Output parent changed during splitting.");
|
|
2085
|
+
if (process.platform !== "win32") {
|
|
2086
|
+
await mkdir3(p.target.path, { mode: 448 });
|
|
2087
|
+
reserved = true;
|
|
2088
|
+
}
|
|
1781
2089
|
await rename2(stage, p.target.path);
|
|
1782
2090
|
reserved = false;
|
|
1783
2091
|
return report;
|
|
@@ -1794,7 +2102,7 @@ import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
|
1794
2102
|
import { createReadStream } from "fs";
|
|
1795
2103
|
import { mkdir as mkdir4, open as open5, readFile as readFile3, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "fs/promises";
|
|
1796
2104
|
import { hostname } from "os";
|
|
1797
|
-
import { join as
|
|
2105
|
+
import { join as join6 } from "path";
|
|
1798
2106
|
function canonicalJson3(value) {
|
|
1799
2107
|
const normalized = JSON.parse(JSON.stringify(value ?? null));
|
|
1800
2108
|
const encode = (entry) => {
|
|
@@ -1869,7 +2177,7 @@ function processAlive(pid) {
|
|
|
1869
2177
|
}
|
|
1870
2178
|
async function readLockOwner(lockPath) {
|
|
1871
2179
|
try {
|
|
1872
|
-
return JSON.parse(await readFile3(
|
|
2180
|
+
return JSON.parse(await readFile3(join6(lockPath, "owner.json"), "utf8"));
|
|
1873
2181
|
} catch {
|
|
1874
2182
|
return null;
|
|
1875
2183
|
}
|
|
@@ -1905,7 +2213,7 @@ async function withAppendLock(lockPath, action, options = {}) {
|
|
|
1905
2213
|
for (; ; ) {
|
|
1906
2214
|
try {
|
|
1907
2215
|
await mkdir4(lockPath, { mode: 448 });
|
|
1908
|
-
await writeFile4(
|
|
2216
|
+
await writeFile4(join6(lockPath, "owner.json"), JSON.stringify({ token, pid: process.pid, host: hostname() }), { mode: 384 });
|
|
1909
2217
|
break;
|
|
1910
2218
|
} catch (error) {
|
|
1911
2219
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -2020,7 +2328,7 @@ async function auditRun(store, runId) {
|
|
|
2020
2328
|
const sha256 = await sha256File(path);
|
|
2021
2329
|
return { path, sha256, matches_recorded: recorded ? sha256 === recorded : null };
|
|
2022
2330
|
}));
|
|
2023
|
-
const manifestPath = state.artifact_dir ?
|
|
2331
|
+
const manifestPath = state.artifact_dir ? join6(state.artifact_dir, "artifact-manifest.json") : null;
|
|
2024
2332
|
const manifestFile = manifestPath && (await stat3(manifestPath).catch(() => null))?.isFile() ? await sha256File(manifestPath).then((sha256) => ({
|
|
2025
2333
|
path: manifestPath,
|
|
2026
2334
|
sha256,
|
|
@@ -2081,9 +2389,25 @@ async function auditRun(store, runId) {
|
|
|
2081
2389
|
}
|
|
2082
2390
|
|
|
2083
2391
|
// src/local-runtime/store.ts
|
|
2084
|
-
import { appendFile, chmod, copyFile, lstat as lstat4, mkdir as mkdir5, readdir as readdir2, readFile as readFile4,
|
|
2085
|
-
import { dirname as
|
|
2392
|
+
import { appendFile, chmod, copyFile, lstat as lstat4, mkdir as mkdir5, readdir as readdir2, readFile as readFile4, rm as rm3, stat as stat4, writeFile as writeFile5 } from "fs/promises";
|
|
2393
|
+
import { dirname as dirname5, join as join7, resolve as resolve3, sep as sep5 } from "path";
|
|
2086
2394
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
2395
|
+
|
|
2396
|
+
// src/local-runtime/atomic-rename.ts
|
|
2397
|
+
import { rename as rename4 } from "fs/promises";
|
|
2398
|
+
async function renameAtomicFile(source, destination) {
|
|
2399
|
+
for (let attempt = 0; ; attempt++) {
|
|
2400
|
+
try {
|
|
2401
|
+
await rename4(source, destination);
|
|
2402
|
+
return;
|
|
2403
|
+
} catch (error) {
|
|
2404
|
+
if (process.platform !== "win32" || attempt >= 10 || !["EPERM", "EACCES", "EBUSY"].includes(error.code ?? "")) throw error;
|
|
2405
|
+
await new Promise((resolve12) => setTimeout(resolve12, 10 * (attempt + 1)));
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
// src/local-runtime/store.ts
|
|
2087
2411
|
function isTerminalRunState(state) {
|
|
2088
2412
|
return state.status === "stage_completed" || state.status === "completed" || state.status === "failed" || state.status === "cancelled" && state.current_stage === "cancelled" && Boolean(state.completed_at);
|
|
2089
2413
|
}
|
|
@@ -2093,10 +2417,10 @@ function defaultLocalHome() {
|
|
|
2093
2417
|
function localStorePaths(root) {
|
|
2094
2418
|
return {
|
|
2095
2419
|
root,
|
|
2096
|
-
specsDir:
|
|
2097
|
-
runsDir:
|
|
2098
|
-
modelsDir:
|
|
2099
|
-
datasetsDir:
|
|
2420
|
+
specsDir: join7(root, "specs"),
|
|
2421
|
+
runsDir: join7(root, "runs"),
|
|
2422
|
+
modelsDir: join7(root, "models"),
|
|
2423
|
+
datasetsDir: join7(root, "datasets")
|
|
2100
2424
|
};
|
|
2101
2425
|
}
|
|
2102
2426
|
async function exists2(path) {
|
|
@@ -2127,7 +2451,7 @@ async function hardenPrivateTree(path) {
|
|
|
2127
2451
|
await chmod(path, 448);
|
|
2128
2452
|
for (const entry of await readdir2(path)) {
|
|
2129
2453
|
try {
|
|
2130
|
-
await hardenPrivateTree(
|
|
2454
|
+
await hardenPrivateTree(join7(path, entry));
|
|
2131
2455
|
} catch (error) {
|
|
2132
2456
|
if (error.code !== "ENOENT") throw error;
|
|
2133
2457
|
}
|
|
@@ -2159,21 +2483,21 @@ async function hardenExistingLocalStore(root) {
|
|
|
2159
2483
|
return true;
|
|
2160
2484
|
}
|
|
2161
2485
|
async function writePrivateJsonAtomic(path, value) {
|
|
2162
|
-
await ensurePrivateDirectory(
|
|
2486
|
+
await ensurePrivateDirectory(dirname5(path));
|
|
2163
2487
|
const tmp = `${path}.${process.pid}.${Date.now()}.${randomUUID6()}.tmp`;
|
|
2164
2488
|
await writeFile5(tmp, `${JSON.stringify(value, null, 2)}
|
|
2165
2489
|
`, {
|
|
2166
2490
|
encoding: "utf8",
|
|
2167
2491
|
mode: 384
|
|
2168
2492
|
});
|
|
2169
|
-
await
|
|
2170
|
-
await
|
|
2493
|
+
await chmod(tmp, 384);
|
|
2494
|
+
await renameAtomicFile(tmp, path);
|
|
2171
2495
|
}
|
|
2172
2496
|
async function readJson2(path) {
|
|
2173
2497
|
return JSON.parse(await readFile4(path, "utf8"));
|
|
2174
2498
|
}
|
|
2175
2499
|
async function appendJsonl(path, value) {
|
|
2176
|
-
await ensurePrivateDirectory(
|
|
2500
|
+
await ensurePrivateDirectory(dirname5(path));
|
|
2177
2501
|
await appendFile(path, `${JSON.stringify(value)}
|
|
2178
2502
|
`, {
|
|
2179
2503
|
encoding: "utf8",
|
|
@@ -2182,13 +2506,13 @@ async function appendJsonl(path, value) {
|
|
|
2182
2506
|
await chmod(path, 384);
|
|
2183
2507
|
}
|
|
2184
2508
|
async function writePrivateText(path, value) {
|
|
2185
|
-
await ensurePrivateDirectory(
|
|
2509
|
+
await ensurePrivateDirectory(dirname5(path));
|
|
2186
2510
|
await writeFile5(path, value, { encoding: "utf8", mode: 384 });
|
|
2187
2511
|
await chmod(path, 384);
|
|
2188
2512
|
}
|
|
2189
2513
|
async function copyIfExists(from, to) {
|
|
2190
2514
|
if (!await exists2(from)) return;
|
|
2191
|
-
await ensurePrivateDirectory(
|
|
2515
|
+
await ensurePrivateDirectory(dirname5(to));
|
|
2192
2516
|
await copyFile(from, to);
|
|
2193
2517
|
await chmod(to, 384);
|
|
2194
2518
|
}
|
|
@@ -2224,15 +2548,15 @@ function isPathInside(root, candidate) {
|
|
|
2224
2548
|
function createLocalStore(root = defaultLocalHome()) {
|
|
2225
2549
|
const resolvedRoot = resolve3(root);
|
|
2226
2550
|
const paths = localStorePaths(resolvedRoot);
|
|
2227
|
-
const runDir = (id) =>
|
|
2228
|
-
const runStatePath = (id) =>
|
|
2229
|
-
const runEventsPath = (id) =>
|
|
2230
|
-
const runRequestPath = (id) =>
|
|
2231
|
-
const runReportPath = (id) =>
|
|
2232
|
-
const cancellationPath = (id) =>
|
|
2233
|
-
const specDir = (id) =>
|
|
2234
|
-
const specPath = (id) =>
|
|
2235
|
-
const modelPath = (id) =>
|
|
2551
|
+
const runDir = (id) => join7(paths.runsDir, id);
|
|
2552
|
+
const runStatePath = (id) => join7(runDir(id), "state.json");
|
|
2553
|
+
const runEventsPath = (id) => join7(runDir(id), "progress.jsonl");
|
|
2554
|
+
const runRequestPath = (id) => join7(runDir(id), "request.json");
|
|
2555
|
+
const runReportPath = (id) => join7(runDir(id), "run-report.json");
|
|
2556
|
+
const cancellationPath = (id) => join7(runDir(id), "cancel.requested");
|
|
2557
|
+
const specDir = (id) => join7(paths.specsDir, id);
|
|
2558
|
+
const specPath = (id) => join7(specDir(id), "spec.json");
|
|
2559
|
+
const modelPath = (id) => join7(paths.modelsDir, id, "model.json");
|
|
2236
2560
|
let ensurePromise;
|
|
2237
2561
|
async function ensureOnce() {
|
|
2238
2562
|
await ensurePrivateDirectory(paths.root);
|
|
@@ -2317,7 +2641,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2317
2641
|
}
|
|
2318
2642
|
async function appendRunEvent(state, event) {
|
|
2319
2643
|
const path = runEventsPath(state.id);
|
|
2320
|
-
await ensurePrivateDirectory(
|
|
2644
|
+
await ensurePrivateDirectory(dirname5(path));
|
|
2321
2645
|
const row = await withAppendLock(`${path}.lock`, async () => {
|
|
2322
2646
|
const { partial } = await readJsonlTail(path);
|
|
2323
2647
|
const { last: previous } = await readJsonlTail(path, (row2) => typeof row2.hash === "string");
|
|
@@ -2331,7 +2655,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2331
2655
|
await appendJsonl(path, chained);
|
|
2332
2656
|
return chained;
|
|
2333
2657
|
});
|
|
2334
|
-
await appendJsonl(
|
|
2658
|
+
await appendJsonl(join7(state.artifact_dir, "progress.jsonl"), {
|
|
2335
2659
|
at: row.occurred_at,
|
|
2336
2660
|
stage: row.stage,
|
|
2337
2661
|
status: row.status,
|
|
@@ -2362,7 +2686,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2362
2686
|
const previous = await getRun(runId);
|
|
2363
2687
|
if (isTerminalRunState(previous)) return previous;
|
|
2364
2688
|
await Promise.all([
|
|
2365
|
-
rm3(
|
|
2689
|
+
rm3(join7(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true }),
|
|
2366
2690
|
rm3(runReportPath(previous.id), { force: true })
|
|
2367
2691
|
]);
|
|
2368
2692
|
if (previous.status === "cancelled" && previous.current_stage === "cancel_requested") return previous;
|
|
@@ -2529,7 +2853,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2529
2853
|
createdAt: completedAt
|
|
2530
2854
|
});
|
|
2531
2855
|
if (!model) {
|
|
2532
|
-
await rm3(
|
|
2856
|
+
await rm3(join7(paths.modelsDir, `local-${report.run_id}`), { recursive: true, force: true });
|
|
2533
2857
|
}
|
|
2534
2858
|
const previous = await getRun(report.run_id);
|
|
2535
2859
|
const state = {
|
|
@@ -2550,7 +2874,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2550
2874
|
if (await exists2(cancellationPath(report.run_id))) {
|
|
2551
2875
|
return preserveCancellationRequestState(report.run_id);
|
|
2552
2876
|
}
|
|
2553
|
-
const manifestPath =
|
|
2877
|
+
const manifestPath = join7(artifactDir, "artifact-manifest.json");
|
|
2554
2878
|
await appendRunEvent(state, {
|
|
2555
2879
|
stage: "completed",
|
|
2556
2880
|
status: "completed",
|
|
@@ -2570,7 +2894,7 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2570
2894
|
const previous = await getRun(runId);
|
|
2571
2895
|
if (options.report) await rm3(runReportPath(previous.id), { force: true });
|
|
2572
2896
|
if (options.model) {
|
|
2573
|
-
await rm3(
|
|
2897
|
+
await rm3(join7(paths.modelsDir, `local-${previous.id}`), { recursive: true, force: true });
|
|
2574
2898
|
}
|
|
2575
2899
|
const state = {
|
|
2576
2900
|
...previous,
|
|
@@ -2690,8 +3014,8 @@ function createLocalStore(root = defaultLocalHome()) {
|
|
|
2690
3014
|
import { createHash as createHash5 } from "crypto";
|
|
2691
3015
|
import { createReadStream as createReadStream2 } from "fs";
|
|
2692
3016
|
import { mkdir as mkdir6, readFile as readFile5, realpath as realpath3, stat as stat5 } from "fs/promises";
|
|
2693
|
-
import { createInterface } from "readline";
|
|
2694
|
-
import { basename as
|
|
3017
|
+
import { createInterface as createInterface2 } from "readline";
|
|
3018
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join8, relative as relative5, resolve as resolve4, sep as sep6 } from "path";
|
|
2695
3019
|
import { asyncBufferFromFile, parquetMetadataAsync as parquetMetadataAsync2, parquetReadObjects as parquetReadObjects2, parquetSchema as parquetSchema2 } from "hyparquet";
|
|
2696
3020
|
import { compressors as compressors2 } from "hyparquet-compressors";
|
|
2697
3021
|
var SPLITS2 = ["training", "validation", "test"];
|
|
@@ -2714,7 +3038,7 @@ function huggingFaceHubCache(modelCache) {
|
|
|
2714
3038
|
return withHuggingFaceCacheEnvironment(process.env, modelCache).HF_HUB_CACHE;
|
|
2715
3039
|
}
|
|
2716
3040
|
function repositoryDirectory(hubCache, repo) {
|
|
2717
|
-
return
|
|
3041
|
+
return join8(hubCache, `datasets--${repo.replaceAll("/", "--")}`);
|
|
2718
3042
|
}
|
|
2719
3043
|
function isStrictDescendant(parent, child) {
|
|
2720
3044
|
const path = relative5(parent, child);
|
|
@@ -2739,7 +3063,7 @@ async function resolveCachedHuggingFaceDataset(args) {
|
|
|
2739
3063
|
const prefetchHint = "Run `tt datasets prefetch` with this spec (for example `tt datasets prefetch tunedtensor.json`) to download it.";
|
|
2740
3064
|
let revision = source.revision;
|
|
2741
3065
|
if (!revision) {
|
|
2742
|
-
const ref = await readFile5(
|
|
3066
|
+
const ref = await readFile5(join8(repository, "refs", "main"), "utf8").catch(() => null);
|
|
2743
3067
|
const parsed = baseModelRevisionSchema.safeParse(ref?.trim());
|
|
2744
3068
|
if (!parsed.success) {
|
|
2745
3069
|
throw new DatasetNotDownloadedError(
|
|
@@ -2748,13 +3072,13 @@ async function resolveCachedHuggingFaceDataset(args) {
|
|
|
2748
3072
|
}
|
|
2749
3073
|
revision = parsed.data;
|
|
2750
3074
|
}
|
|
2751
|
-
const snapshot =
|
|
3075
|
+
const snapshot = join8(repository, "snapshots", revision);
|
|
2752
3076
|
const physicalRepository = await realpath3(repository).catch(() => null);
|
|
2753
|
-
const blobs = physicalRepository ?
|
|
3077
|
+
const blobs = physicalRepository ? join8(physicalRepository, "blobs") : null;
|
|
2754
3078
|
const files = {};
|
|
2755
3079
|
const localPaths = {};
|
|
2756
3080
|
for (const [split, path] of Object.entries(datasetFiles(args.dataset))) {
|
|
2757
|
-
const local =
|
|
3081
|
+
const local = join8(snapshot, ...path.split(/[/\\]/));
|
|
2758
3082
|
const metadata = await stat5(local).catch(() => null);
|
|
2759
3083
|
if (!metadata || !physicalRepository || !blobs) {
|
|
2760
3084
|
throw new DatasetNotDownloadedError(
|
|
@@ -2767,7 +3091,7 @@ async function resolveCachedHuggingFaceDataset(args) {
|
|
|
2767
3091
|
}
|
|
2768
3092
|
if (metadata.size === 0) throw new Error(`Cached dataset file ${path} (${split}) is empty: ${local}`);
|
|
2769
3093
|
const digests = await digestFile(target, metadata.size);
|
|
2770
|
-
const blobName = isStrictDescendant(blobs, target) ?
|
|
3094
|
+
const blobName = isStrictDescendant(blobs, target) ? basename3(target).toLowerCase() : "";
|
|
2771
3095
|
const expected = /^[0-9a-f]{64}$/.test(blobName) ? digests.sha256 : /^[0-9a-f]{40}$/.test(blobName) ? digests.gitSha1 : null;
|
|
2772
3096
|
if (expected !== null && expected !== blobName) {
|
|
2773
3097
|
throw new Error(
|
|
@@ -2812,7 +3136,7 @@ function columnNames(columns) {
|
|
|
2812
3136
|
return "messages" in columns ? [columns.messages] : [columns.input, columns.output];
|
|
2813
3137
|
}
|
|
2814
3138
|
async function* jsonlRecords(path, split) {
|
|
2815
|
-
const lines =
|
|
3139
|
+
const lines = createInterface2({ input: createReadStream2(path, { encoding: "utf8" }), crlfDelay: Infinity });
|
|
2816
3140
|
let index = -1;
|
|
2817
3141
|
for await (const line of lines) {
|
|
2818
3142
|
index += 1;
|
|
@@ -2925,7 +3249,7 @@ async function convertMappedDataset(args) {
|
|
|
2925
3249
|
if (examples.length === 0) {
|
|
2926
3250
|
throw new Error(`No usable ${split} records remain after applying dataset_prebuilt.huggingface.columns.`);
|
|
2927
3251
|
}
|
|
2928
|
-
const output =
|
|
3252
|
+
const output = join8(args.outputRoot, `${split}-${key}.jsonl`);
|
|
2929
3253
|
const content = `${examples.map((example) => JSON.stringify(exampleToChatRow(args.spec, example))).join("\n")}
|
|
2930
3254
|
`;
|
|
2931
3255
|
if (await readFile5(output, "utf8").catch(() => null) !== content) {
|
|
@@ -2987,9 +3311,9 @@ async function prefetchHuggingFaceDataset(args) {
|
|
|
2987
3311
|
`${safeName(dataset.huggingface.repo)}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`
|
|
2988
3312
|
);
|
|
2989
3313
|
await mkdir6(artifactDir, { recursive: true });
|
|
2990
|
-
const inputPath =
|
|
2991
|
-
const outputPath =
|
|
2992
|
-
const logPath =
|
|
3314
|
+
const inputPath = join8(artifactDir, "prefetch-input.json");
|
|
3315
|
+
const outputPath = join8(artifactDir, "prefetch-output.json");
|
|
3316
|
+
const logPath = join8(artifactDir, "prefetch.log");
|
|
2993
3317
|
await writeJson(inputPath, {
|
|
2994
3318
|
repo: dataset.huggingface.repo,
|
|
2995
3319
|
...dataset.huggingface.revision ? { revision: dataset.huggingface.revision } : {},
|
|
@@ -3096,13 +3420,13 @@ import { createReadStream as createReadStream3 } from "fs";
|
|
|
3096
3420
|
import { createHash as createHash8, randomUUID as randomUUID8 } from "crypto";
|
|
3097
3421
|
import { performance } from "perf_hooks";
|
|
3098
3422
|
import { homedir as homedir2 } from "os";
|
|
3099
|
-
import { basename as
|
|
3423
|
+
import { basename as basename6, dirname as dirname8, join as join12, relative as relative6, resolve as resolve8 } from "path";
|
|
3100
3424
|
|
|
3101
3425
|
// src/local-runtime/gpu-executor.ts
|
|
3102
3426
|
import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
|
|
3103
3427
|
import { mkdtemp, mkdir as mkdir7, realpath as realpath4, rm as rm4, stat as stat6, writeFile as writeFile6 } from "fs/promises";
|
|
3104
3428
|
import { tmpdir } from "os";
|
|
3105
|
-
import { basename as
|
|
3429
|
+
import { basename as basename4, dirname as dirname6, join as join9, resolve as resolve5 } from "path";
|
|
3106
3430
|
function shellQuote(value) {
|
|
3107
3431
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3108
3432
|
}
|
|
@@ -3172,14 +3496,14 @@ function gpuBaseModelPath(config, model, revision) {
|
|
|
3172
3496
|
throw new Error("AWS GPU execution needs an immutable base-model revision. Run tt models prefetch first or configure paths.baseModel.");
|
|
3173
3497
|
}
|
|
3174
3498
|
const env = withHuggingFaceCacheEnvironment(process.env, config.paths.modelCache);
|
|
3175
|
-
return
|
|
3499
|
+
return join9(env.HF_HUB_CACHE, `models--${model.replaceAll("/", "--")}`, "snapshots", revision);
|
|
3176
3500
|
}
|
|
3177
3501
|
async function physicalTransferPath(path) {
|
|
3178
3502
|
try {
|
|
3179
3503
|
return await realpath4(path);
|
|
3180
3504
|
} catch (error) {
|
|
3181
|
-
if (error.code !== "ENOENT" ||
|
|
3182
|
-
return
|
|
3505
|
+
if (error.code !== "ENOENT" || dirname6(path) === path) throw error;
|
|
3506
|
+
return join9(await physicalTransferPath(dirname6(path)), basename4(path));
|
|
3183
3507
|
}
|
|
3184
3508
|
}
|
|
3185
3509
|
async function assertSeparateTransferPaths(files) {
|
|
@@ -3202,9 +3526,9 @@ async function runGpuProcess(args) {
|
|
|
3202
3526
|
const options = sshOptions(args.gpu);
|
|
3203
3527
|
const identity = args.logPath ? createHash6("sha256").update(resolve5(args.logPath)).digest("hex").slice(0, 32) : randomUUID7();
|
|
3204
3528
|
const root = `/tmp/tt-gpu-${identity}`;
|
|
3205
|
-
const scratch = await mkdtemp(
|
|
3206
|
-
const runtime =
|
|
3207
|
-
const mappings = args.files.map((file, index) => ({ ...file, path: resolve5(file.path), remote: `${root}/files/${index}/${
|
|
3529
|
+
const scratch = await mkdtemp(join9(tmpdir(), "tt-gpu-"));
|
|
3530
|
+
const runtime = join9(localRuntimePackageRoot(import.meta.url), "training", args.runtime);
|
|
3531
|
+
const mappings = args.files.map((file, index) => ({ ...file, path: resolve5(file.path), remote: `${root}/files/${index}/${basename4(file.path)}` }));
|
|
3208
3532
|
const mapPath = (path) => {
|
|
3209
3533
|
const absolute = resolve5(path);
|
|
3210
3534
|
const match = [...mappings].sort((a, b) => b.path.length - a.path.length).find((file) => absolute === file.path || file.directory && absolute.startsWith(`${file.path}/`));
|
|
@@ -3255,10 +3579,10 @@ async function runGpuProcess(args) {
|
|
|
3255
3579
|
throw new Error(`Cannot reserve GPU staging directory ${host}:${root}. Another process or unrecovered artifacts may be present. ${error instanceof Error ? error.message : error}`);
|
|
3256
3580
|
});
|
|
3257
3581
|
for (const name of ["pyproject.toml", "uv.lock", "src"]) {
|
|
3258
|
-
await sync(
|
|
3582
|
+
await sync(join9(runtime, name), `${root}/runtime/`, true);
|
|
3259
3583
|
}
|
|
3260
3584
|
for (const file of mappings) {
|
|
3261
|
-
await ssh(`mkdir -p ${shellQuote(file.directory ? file.remote :
|
|
3585
|
+
await ssh(`mkdir -p ${shellQuote(file.directory ? file.remote : dirname6(file.remote))}`);
|
|
3262
3586
|
if (file.direction !== "output") {
|
|
3263
3587
|
const exists3 = await stat6(file.path).catch((error) => {
|
|
3264
3588
|
if (error.code === "ENOENT" && file.direction === "both") return void 0;
|
|
@@ -3270,7 +3594,7 @@ async function runGpuProcess(args) {
|
|
|
3270
3594
|
if (args.document) {
|
|
3271
3595
|
const value = { ...args.document.value };
|
|
3272
3596
|
for (const key of args.document.pathKeys) if (typeof value[key] === "string") value[key] = mapPath(value[key]);
|
|
3273
|
-
const path =
|
|
3597
|
+
const path = join9(scratch, "document.json");
|
|
3274
3598
|
await writeFile6(path, JSON.stringify(value), { mode: 384 });
|
|
3275
3599
|
await sync(path, mapPath(args.document.path), true);
|
|
3276
3600
|
}
|
|
@@ -3303,7 +3627,7 @@ async function runGpuProcess(args) {
|
|
|
3303
3627
|
if (started) {
|
|
3304
3628
|
await ssh(`bash -c ${shellQuote(`if test -f ${root}/pid; then pid=$(cat ${root}/pid); ${stop}; fi`)}`, false);
|
|
3305
3629
|
for (const file of mappings.filter((file2) => file2.direction !== "input")) {
|
|
3306
|
-
await mkdir7(file.directory ? file.path :
|
|
3630
|
+
await mkdir7(file.directory ? file.path : dirname6(file.path), { recursive: true });
|
|
3307
3631
|
if (!completed && !file.directory) {
|
|
3308
3632
|
const exists3 = await runLoggedProcess({ command: "ssh", commandArgs: [...options, host, `test -e ${shellQuote(file.remote)}`], stage: args.stage, timeoutMs: 3e4 });
|
|
3309
3633
|
if (exists3.exitCode === 1) continue;
|
|
@@ -3324,7 +3648,7 @@ async function runGpuProcess(args) {
|
|
|
3324
3648
|
// src/local-runtime/evaluation.ts
|
|
3325
3649
|
import { createHash as createHash7 } from "crypto";
|
|
3326
3650
|
import { mkdir as mkdir8, readFile as readFile6, rm as rm5, stat as stat7, writeFile as writeFile7 } from "fs/promises";
|
|
3327
|
-
import { basename as
|
|
3651
|
+
import { basename as basename5, dirname as dirname7, join as join10, resolve as resolve6 } from "path";
|
|
3328
3652
|
function normalize(value) {
|
|
3329
3653
|
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
3330
3654
|
}
|
|
@@ -3491,7 +3815,7 @@ function aggregateJsonFieldMetrics(scores, total) {
|
|
|
3491
3815
|
}
|
|
3492
3816
|
function fileUriToPath(value) {
|
|
3493
3817
|
if (!value) return void 0;
|
|
3494
|
-
return value.startsWith("file://") ? value
|
|
3818
|
+
return value.startsWith("file://") ? localModelArtifactPath(value) : value;
|
|
3495
3819
|
}
|
|
3496
3820
|
var INFERENCE_PROTOCOL_VERSION = 2;
|
|
3497
3821
|
function parseInferenceProgress(line, total) {
|
|
@@ -3555,8 +3879,8 @@ async function runTransformersInference(args) {
|
|
|
3555
3879
|
const inputPath = `${args.outputPath}.inference-input.json`;
|
|
3556
3880
|
const outputPath = `${args.outputPath}.inference-output.json`;
|
|
3557
3881
|
const logPath = `${args.outputPath}.inference.log`;
|
|
3558
|
-
const checkpointPath = args.checkpointPath ??
|
|
3559
|
-
await mkdir8(
|
|
3882
|
+
const checkpointPath = args.checkpointPath ?? join10(dirname7(args.outputPath), "inference-checkpoints", basename5(args.outputPath));
|
|
3883
|
+
await mkdir8(dirname7(inputPath), { recursive: true });
|
|
3560
3884
|
const payload = {
|
|
3561
3885
|
protocol_version: INFERENCE_PROTOCOL_VERSION,
|
|
3562
3886
|
kind: args.kind,
|
|
@@ -3755,7 +4079,7 @@ function baselineStoreRoot(config) {
|
|
|
3755
4079
|
return config.storeRoot ? resolve6(config.storeRoot) : defaultLocalHome();
|
|
3756
4080
|
}
|
|
3757
4081
|
function baselineCachePath(config, key) {
|
|
3758
|
-
return
|
|
4082
|
+
return join10(baselineStoreRoot(config), "cache", "baseline-evals", `${key}.json`);
|
|
3759
4083
|
}
|
|
3760
4084
|
async function readBaselineCache(config, path) {
|
|
3761
4085
|
await hardenExistingLocalStore(baselineStoreRoot(config));
|
|
@@ -3882,7 +4206,7 @@ async function evaluateExamples(args) {
|
|
|
3882
4206
|
}
|
|
3883
4207
|
if (cacheKey) {
|
|
3884
4208
|
await createLocalStore(baselineStoreRoot(args.config)).ensure();
|
|
3885
|
-
await mkdir8(
|
|
4209
|
+
await mkdir8(dirname7(baselineCachePath(args.config, cacheKey)), { recursive: true, mode: 448 });
|
|
3886
4210
|
await hardenExistingLocalStore(baselineStoreRoot(args.config));
|
|
3887
4211
|
}
|
|
3888
4212
|
const inference = args.config.dryRun ? null : await runTransformersInference({
|
|
@@ -3962,7 +4286,7 @@ function compareEvalReports(baseline, candidate) {
|
|
|
3962
4286
|
|
|
3963
4287
|
// src/local-runtime/process-training.ts
|
|
3964
4288
|
import { access, mkdir as mkdir9, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
3965
|
-
import { join as
|
|
4289
|
+
import { join as join11, resolve as resolve7 } from "path";
|
|
3966
4290
|
function buildTrainingHyperparameters(request, options = {}) {
|
|
3967
4291
|
const model = resolveTrainingModel(request.spec_snapshot.base_model);
|
|
3968
4292
|
const hyper = request.hyperparameters;
|
|
@@ -4058,7 +4382,7 @@ async function launchProcessTraining(args) {
|
|
|
4058
4382
|
const hyperparameters = buildTrainingHyperparameters(request, {
|
|
4059
4383
|
baseModelRevision: args.baseModelRevision
|
|
4060
4384
|
});
|
|
4061
|
-
await writeJson(
|
|
4385
|
+
await writeJson(join11(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
|
|
4062
4386
|
const entrypoint = buildBundledPythonCommand("train.py");
|
|
4063
4387
|
if (config.dryRun) {
|
|
4064
4388
|
await writeFile8(
|
|
@@ -4137,12 +4461,12 @@ async function launchProcessTraining(args) {
|
|
|
4137
4461
|
onLine: (line) => forwardTrainingProgress(line),
|
|
4138
4462
|
shouldCancel: args.shouldCancel
|
|
4139
4463
|
});
|
|
4140
|
-
const metricsPath =
|
|
4464
|
+
const metricsPath = join11(artifacts.trainingModelDir, "training-metrics.json");
|
|
4141
4465
|
const metrics = await pathExists(metricsPath) ? JSON.parse(await readFile7(metricsPath, "utf8")) : null;
|
|
4142
4466
|
if (exitCode !== 0) {
|
|
4143
4467
|
throw new Error(`Training process exited with code ${exitCode}. See ${artifacts.trainingLog}.`);
|
|
4144
4468
|
}
|
|
4145
|
-
const modelTarPath =
|
|
4469
|
+
const modelTarPath = join11(artifacts.trainingOutputDir, "model.tar.gz");
|
|
4146
4470
|
const modelUri = await pathExists(modelTarPath) ? fileUri(modelTarPath) : fileUri(artifacts.trainingModelDir);
|
|
4147
4471
|
await args.reporter?.onEvent?.({
|
|
4148
4472
|
stage: "training",
|
|
@@ -4246,7 +4570,7 @@ function parseLocalRunnerConfig(document, path) {
|
|
|
4246
4570
|
const config = localRunnerConfigSchema.parse(document);
|
|
4247
4571
|
if (!path) return config;
|
|
4248
4572
|
const configPath = resolve8(path);
|
|
4249
|
-
const base =
|
|
4573
|
+
const base = dirname8(configPath);
|
|
4250
4574
|
const configPathValue = (value) => {
|
|
4251
4575
|
if (!value) return void 0;
|
|
4252
4576
|
if (value === "~") return homedir2();
|
|
@@ -4284,7 +4608,7 @@ function elapsed(started) {
|
|
|
4284
4608
|
return { ms, seconds: Math.round(ms / 1e3 * 1e3) / 1e3 };
|
|
4285
4609
|
}
|
|
4286
4610
|
function stripFileUri(path) {
|
|
4287
|
-
return path.
|
|
4611
|
+
return path.startsWith("file://") ? localModelArtifactPath(path) : path;
|
|
4288
4612
|
}
|
|
4289
4613
|
async function pathExists2(path) {
|
|
4290
4614
|
try {
|
|
@@ -4582,8 +4906,8 @@ async function readStageMetadata(path) {
|
|
|
4582
4906
|
}
|
|
4583
4907
|
}
|
|
4584
4908
|
async function removePrefixedArtifacts(path) {
|
|
4585
|
-
const directory =
|
|
4586
|
-
const prefix = `${
|
|
4909
|
+
const directory = dirname8(path);
|
|
4910
|
+
const prefix = `${basename6(path)}.`;
|
|
4587
4911
|
const names = await readdir3(directory).catch(() => []);
|
|
4588
4912
|
await Promise.all([
|
|
4589
4913
|
rm6(path, { force: true }),
|
|
@@ -4654,7 +4978,7 @@ async function throwIfCancelled(store, request) {
|
|
|
4654
4978
|
}
|
|
4655
4979
|
}
|
|
4656
4980
|
async function acquireWorkflowLock(lockPath, description) {
|
|
4657
|
-
await mkdir10(
|
|
4981
|
+
await mkdir10(dirname8(lockPath), { recursive: true });
|
|
4658
4982
|
const token = randomUUID8();
|
|
4659
4983
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4660
4984
|
try {
|
|
@@ -4713,7 +5037,7 @@ async function acquireRunLock(store, runId) {
|
|
|
4713
5037
|
}
|
|
4714
5038
|
async function acquireArtifactLock(artifacts) {
|
|
4715
5039
|
return acquireWorkflowLock(
|
|
4716
|
-
|
|
5040
|
+
join12(artifacts.runDir, ARTIFACT_WORKFLOW_LOCK_FILE),
|
|
4717
5041
|
`Artifact directory ${artifacts.runDir}`
|
|
4718
5042
|
);
|
|
4719
5043
|
}
|
|
@@ -5573,7 +5897,7 @@ async function runLocalPipeline(input) {
|
|
|
5573
5897
|
store,
|
|
5574
5898
|
reporter: input.reporter
|
|
5575
5899
|
});
|
|
5576
|
-
await writeJsonAtomic(
|
|
5900
|
+
await writeJsonAtomic(join12(artifacts.runDir, "resolved-workflow.json"), {
|
|
5577
5901
|
spec: input.projectSpec ?? {
|
|
5578
5902
|
...input.request.spec_snapshot,
|
|
5579
5903
|
hyperparameters: input.request.hyperparameters,
|
|
@@ -5595,7 +5919,7 @@ async function runLocalPipeline(input) {
|
|
|
5595
5919
|
if (step.uses === "evaluate") {
|
|
5596
5920
|
const isBase = step.with.model === "base";
|
|
5597
5921
|
const report = isBase ? await runBaselineStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter }) : await runCandidateStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter });
|
|
5598
|
-
const reportPath =
|
|
5922
|
+
const reportPath = join12(artifacts.runDir, "evaluations", `${step.id}.json`);
|
|
5599
5923
|
await writeJsonAtomic(reportPath, { ...report, artifact_uri: fileUri(reportPath) });
|
|
5600
5924
|
outputs[step.id] = { report: { ...report, artifact_uri: fileUri(reportPath) }, reportPath };
|
|
5601
5925
|
await refreshArtifactManifest(prepared);
|
|
@@ -5674,7 +5998,7 @@ async function runLocalFineTune(input) {
|
|
|
5674
5998
|
// src/local-runtime/project-workflow.ts
|
|
5675
5999
|
import { existsSync } from "fs";
|
|
5676
6000
|
import { readFile as readFile9 } from "fs/promises";
|
|
5677
|
-
import { basename as
|
|
6001
|
+
import { basename as basename7, dirname as dirname9, join as join13, resolve as resolve9 } from "path";
|
|
5678
6002
|
function configForSpec(spec, specPath) {
|
|
5679
6003
|
return parseLocalRunnerConfig({
|
|
5680
6004
|
storeRoot: process.env.TT_LOCAL_HOME?.trim() ? expandUserPath(process.env.TT_LOCAL_HOME.trim()) : DEFAULT_PROJECT_STORE_ROOT,
|
|
@@ -5695,10 +6019,10 @@ async function resolveDownloadConfig(specPath, configPath, warn) {
|
|
|
5695
6019
|
async function resolveProjectConfig(specPath, configPath, warn, loaded) {
|
|
5696
6020
|
const input = loaded ?? (existsSync(specPath) ? await loadLocalRunInput(specPath) : void 0);
|
|
5697
6021
|
const spec = input && input.kind !== "request" ? input.spec : void 0;
|
|
5698
|
-
if (!configPath && spec?.runtime && /^\..+\.tt-agent-[0-9a-f-]+\.json$/.test(
|
|
6022
|
+
if (!configPath && spec?.runtime && /^\..+\.tt-agent-[0-9a-f-]+\.json$/.test(basename7(specPath))) {
|
|
5699
6023
|
return { config: configForSpec(spec, specPath) };
|
|
5700
6024
|
}
|
|
5701
|
-
const candidate = configPath ? resolve9(configPath) :
|
|
6025
|
+
const candidate = configPath ? resolve9(configPath) : join13(dirname9(resolve9(specPath)), "local-runner.json");
|
|
5702
6026
|
if (configPath || existsSync(candidate)) {
|
|
5703
6027
|
const legacy = parseLocalRunnerConfig(JSON.parse(await readFile9(candidate, "utf8")), candidate);
|
|
5704
6028
|
if (spec?.runtime || spec?.evaluation) {
|
|
@@ -5714,15 +6038,15 @@ async function resolveProjectConfig(specPath, configPath, warn, loaded) {
|
|
|
5714
6038
|
import { constants as constants5 } from "fs";
|
|
5715
6039
|
import { access as access2, statfs as statfs2 } from "fs/promises";
|
|
5716
6040
|
import { cpus as cpus2, homedir as homedir4, type } from "os";
|
|
5717
|
-
import { dirname as
|
|
5718
|
-
import { spawn } from "child_process";
|
|
6041
|
+
import { dirname as dirname11, join as join15, resolve as resolve11 } from "path";
|
|
6042
|
+
import { spawn as spawn2 } from "child_process";
|
|
5719
6043
|
|
|
5720
6044
|
// src/local-runtime/live-usage.ts
|
|
5721
6045
|
import { execFile } from "child_process";
|
|
5722
6046
|
import { readFileSync } from "fs";
|
|
5723
6047
|
import { statfs } from "fs/promises";
|
|
5724
6048
|
import { cpus, freemem, homedir as homedir3, loadavg, totalmem } from "os";
|
|
5725
|
-
import { dirname as
|
|
6049
|
+
import { dirname as dirname10, join as join14, resolve as resolve10 } from "path";
|
|
5726
6050
|
function parseMacMemory(stdout, total) {
|
|
5727
6051
|
const pageSize = Number(/page size of (\d+) bytes/.exec(stdout)?.[1]);
|
|
5728
6052
|
const pages = (label) => {
|
|
@@ -5861,7 +6185,7 @@ function cpuModel(reported, platform, arch) {
|
|
|
5861
6185
|
}
|
|
5862
6186
|
}
|
|
5863
6187
|
async function modelCacheDisk(env) {
|
|
5864
|
-
const path = resolve10(env.HF_HOME?.trim() ||
|
|
6188
|
+
const path = resolve10(env.HF_HOME?.trim() || join14(homedir3(), ".cache", "huggingface"));
|
|
5865
6189
|
for (let probe = path, depth = 0; depth < 8; depth += 1) {
|
|
5866
6190
|
try {
|
|
5867
6191
|
const fs = await statfs(probe);
|
|
@@ -5871,7 +6195,7 @@ async function modelCacheDisk(env) {
|
|
|
5871
6195
|
total_bytes: Number(fs.blocks) * Number(fs.bsize)
|
|
5872
6196
|
};
|
|
5873
6197
|
} catch {
|
|
5874
|
-
const parent =
|
|
6198
|
+
const parent = dirname10(probe);
|
|
5875
6199
|
if (parent === probe) break;
|
|
5876
6200
|
probe = parent;
|
|
5877
6201
|
}
|
|
@@ -5932,7 +6256,7 @@ async function runCommand(command, args, options = {}) {
|
|
|
5932
6256
|
let stderr = "";
|
|
5933
6257
|
let stopError;
|
|
5934
6258
|
let forceKillTimer = null;
|
|
5935
|
-
const child =
|
|
6259
|
+
const child = spawn2(command, args, {
|
|
5936
6260
|
cwd: options.cwd,
|
|
5937
6261
|
env: options.env ?? process.env,
|
|
5938
6262
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6093,7 +6417,7 @@ async function diskCheck(name, path) {
|
|
|
6093
6417
|
} catch (error) {
|
|
6094
6418
|
let probe = resolvedPath;
|
|
6095
6419
|
for (let i = 0; i < 6; i += 1) {
|
|
6096
|
-
const parent =
|
|
6420
|
+
const parent = dirname11(probe);
|
|
6097
6421
|
if (parent === probe) break;
|
|
6098
6422
|
probe = parent;
|
|
6099
6423
|
try {
|
|
@@ -6182,10 +6506,10 @@ async function collectHostInventory(options = {}) {
|
|
|
6182
6506
|
const cwd = options.cwd ?? process.cwd();
|
|
6183
6507
|
const nodeVersion = process.versions.node;
|
|
6184
6508
|
const nodeMajor = Number(nodeVersion.split(".")[0]);
|
|
6185
|
-
const artifactRoot = resolve11(cwd, config?.artifactRoot ??
|
|
6509
|
+
const artifactRoot = resolve11(cwd, config?.artifactRoot ?? join15(cwd, DEFAULT_ARTIFACT_ROOT));
|
|
6186
6510
|
const storeRoot = resolve11(config?.storeRoot ?? defaultStoreRoot(env));
|
|
6187
6511
|
const modelCache = resolve11(
|
|
6188
|
-
config?.paths.modelCache ?? env.HF_HOME ??
|
|
6512
|
+
config?.paths.modelCache ?? env.HF_HOME ?? join15(homedir4(), ".cache", "huggingface")
|
|
6189
6513
|
);
|
|
6190
6514
|
const [artifactDisk, storeDisk, cacheDisk, uvVersion, nvidiaQueried, memory] = await Promise.all([
|
|
6191
6515
|
diskCheck("artifact-root", artifactRoot),
|
|
@@ -6673,11 +6997,11 @@ function warningsFromSnapshot(report, target) {
|
|
|
6673
6997
|
|
|
6674
6998
|
// src/local-runtime/hardware-snapshot.ts
|
|
6675
6999
|
import { chmod as chmod2, mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
6676
|
-
import { dirname as
|
|
7000
|
+
import { dirname as dirname12, join as join16 } from "path";
|
|
6677
7001
|
var HARDWARE_SNAPSHOT_VERSION = 1;
|
|
6678
7002
|
var HARDWARE_SNAPSHOT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6679
7003
|
function hardwareSnapshotPath(env = process.env) {
|
|
6680
|
-
return
|
|
7004
|
+
return join16(getTunedTensorHome(env), "hardware.json");
|
|
6681
7005
|
}
|
|
6682
7006
|
async function readHardwareSnapshot(env = process.env) {
|
|
6683
7007
|
const path = hardwareSnapshotPath(env);
|
|
@@ -6703,7 +7027,7 @@ async function readHardwareSnapshot(env = process.env) {
|
|
|
6703
7027
|
}
|
|
6704
7028
|
async function writeHardwareSnapshot(snapshot, env = process.env) {
|
|
6705
7029
|
const path = hardwareSnapshotPath(env);
|
|
6706
|
-
await mkdir11(
|
|
7030
|
+
await mkdir11(dirname12(path), { recursive: true, mode: 448 });
|
|
6707
7031
|
const gpu = snapshot.capabilities.gpu;
|
|
6708
7032
|
const record = {
|
|
6709
7033
|
version: HARDWARE_SNAPSHOT_VERSION,
|
|
@@ -6882,6 +7206,7 @@ export {
|
|
|
6882
7206
|
executeDatasetSplit,
|
|
6883
7207
|
checkAwsGpu,
|
|
6884
7208
|
runGpuProcess,
|
|
7209
|
+
renameAtomicFile,
|
|
6885
7210
|
chainEvent,
|
|
6886
7211
|
verifyEventChain,
|
|
6887
7212
|
withAppendLock,
|
|
@@ -6926,4 +7251,4 @@ export {
|
|
|
6926
7251
|
assessHardware,
|
|
6927
7252
|
createRunConsoleReporter
|
|
6928
7253
|
};
|
|
6929
|
-
//# sourceMappingURL=chunk-
|
|
7254
|
+
//# sourceMappingURL=chunk-7CO5DBCY.js.map
|